mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 03:08:54 +01:00
49582519c1
This change adds a new compiler directive 'kage:unit' to Kage. This takes one of these two values: 'pixel' and 'texel'. The default value is 'texel'. With the pixel-unit mode, all the built-in functions treats pixels instead of texels, and the texCoord argument of Fragment is in pixels. This simplifies shader programs as programs no longer have the notion of texels. With the texel-unit mode, the behavior is the same as the current behavior. Closes #1431
196 lines
5.2 KiB
Go
196 lines
5.2 KiB
Go
// 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 ebiten
|
|
|
|
import (
|
|
"fmt"
|
|
"image"
|
|
"math"
|
|
"sync/atomic"
|
|
|
|
"github.com/hajimehoshi/ebiten/v2/internal/atlas"
|
|
"github.com/hajimehoshi/ebiten/v2/internal/ui"
|
|
)
|
|
|
|
const screenShaderSrc = `//kage:unit pixel
|
|
|
|
package main
|
|
|
|
func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
|
|
_, dr := imageDstRegionOnTexture()
|
|
_, sr := imageSrcRegionOnTexture()
|
|
scale := dr/sr
|
|
|
|
// Shift 1/512 [pixel] to avoid the tie-breaking issue.
|
|
pos := texCoord
|
|
p0 := pos - 1/2.0 + 1/512.0
|
|
p1 := pos + 1/2.0 + 1/512.0
|
|
|
|
// Texels must be in the source rect, so it is not necessary to check.
|
|
c0 := imageSrc0UnsafeAt(p0)
|
|
c1 := imageSrc0UnsafeAt(vec2(p1.x, p0.y))
|
|
c2 := imageSrc0UnsafeAt(vec2(p0.x, p1.y))
|
|
c3 := imageSrc0UnsafeAt(p1)
|
|
|
|
// p is the p1 value in one pixel assuming that the pixel's upper-left is (0, 0) and the lower-right is (1, 1).
|
|
p := fract(p1)
|
|
|
|
// rate indicates how much the 4 colors are mixed. rate is in between [0, 1].
|
|
//
|
|
// 0 <= p <= 1/Scale: The rate is in between [0, 1]
|
|
// 1/Scale < p: Don't care. Adjacent colors (e.g. c0 vs c1 in an X direction) should be the same.
|
|
rate := clamp(p*scale, 0, 1)
|
|
return mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y)
|
|
}
|
|
`
|
|
|
|
var screenFilterEnabled = int32(1)
|
|
|
|
func isScreenFilterEnabled() bool {
|
|
return atomic.LoadInt32(&screenFilterEnabled) != 0
|
|
}
|
|
|
|
func setScreenFilterEnabled(enabled bool) {
|
|
v := int32(0)
|
|
if enabled {
|
|
v = 1
|
|
}
|
|
atomic.StoreInt32(&screenFilterEnabled, v)
|
|
}
|
|
|
|
type gameForUI struct {
|
|
game Game
|
|
offscreen *Image
|
|
screen *Image
|
|
screenShader *Shader
|
|
imageDumper imageDumper
|
|
transparent bool
|
|
}
|
|
|
|
func newGameForUI(game Game, transparent bool) *gameForUI {
|
|
g := &gameForUI{
|
|
game: game,
|
|
transparent: transparent,
|
|
}
|
|
|
|
s, err := NewShader([]byte(screenShaderSrc))
|
|
if err != nil {
|
|
panic(fmt.Sprintf("ebiten: compiling the screen shader failed: %v", err))
|
|
}
|
|
g.screenShader = s
|
|
|
|
return g
|
|
}
|
|
|
|
func (g *gameForUI) NewOffscreenImage(width, height int) *ui.Image {
|
|
if g.offscreen != nil {
|
|
g.offscreen.Dispose()
|
|
g.offscreen = nil
|
|
}
|
|
|
|
// Keep the offscreen an unmanaged image that is always isolated 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.
|
|
imageType := atlas.ImageTypeUnmanaged
|
|
if ui.IsScreenClearedEveryFrame() {
|
|
// A violatile image is also always isolated.
|
|
imageType = atlas.ImageTypeVolatile
|
|
}
|
|
g.offscreen = newImage(image.Rect(0, 0, width, height), imageType)
|
|
return g.offscreen.image
|
|
}
|
|
|
|
func (g *gameForUI) NewScreenImage(width, height int) *ui.Image {
|
|
if g.screen != nil {
|
|
g.screen.Dispose()
|
|
g.screen = nil
|
|
}
|
|
|
|
g.screen = newImage(image.Rect(0, 0, width, height), atlas.ImageTypeScreen)
|
|
return g.screen.image
|
|
}
|
|
|
|
func (g *gameForUI) Layout(outsideWidth, outsideHeight float64) (float64, float64) {
|
|
if l, ok := g.game.(LayoutFer); ok {
|
|
return l.LayoutF(outsideWidth, outsideHeight)
|
|
}
|
|
|
|
// Even if the original value is less than 1, the value must be a positive integer (#2340).
|
|
// This is for a simple implementation of Layout, which returns the argument values without modifications.
|
|
// TODO: Remove this hack when Game.Layout takes floats instead of integers.
|
|
if outsideWidth < 1 {
|
|
outsideWidth = 1
|
|
}
|
|
if outsideHeight < 1 {
|
|
outsideHeight = 1
|
|
}
|
|
|
|
// TODO: Add a new Layout function taking float values (#2285).
|
|
sw, sh := g.game.Layout(int(outsideWidth), int(outsideHeight))
|
|
return float64(sw), float64(sh)
|
|
}
|
|
|
|
func (g *gameForUI) UpdateInputState(fn func(*ui.InputState)) {
|
|
theInputState.update(fn)
|
|
}
|
|
|
|
func (g *gameForUI) Update() error {
|
|
if err := g.game.Update(); err != nil {
|
|
return err
|
|
}
|
|
if err := g.imageDumper.update(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *gameForUI) DrawOffscreen() error {
|
|
g.game.Draw(g.offscreen)
|
|
if err := g.imageDumper.dump(g.offscreen, g.transparent); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *gameForUI) DrawFinalScreen(scale, offsetX, offsetY float64) {
|
|
var geoM GeoM
|
|
geoM.Scale(scale, scale)
|
|
geoM.Translate(offsetX, offsetY)
|
|
|
|
if d, ok := g.game.(FinalScreenDrawer); ok {
|
|
d.DrawFinalScreen(g.screen, g.offscreen, geoM)
|
|
return
|
|
}
|
|
|
|
switch {
|
|
case !isScreenFilterEnabled(), math.Floor(scale) == scale:
|
|
op := &DrawImageOptions{}
|
|
op.GeoM = geoM
|
|
g.screen.DrawImage(g.offscreen, op)
|
|
case scale < 1:
|
|
op := &DrawImageOptions{}
|
|
op.GeoM = geoM
|
|
op.Filter = FilterLinear
|
|
g.screen.DrawImage(g.offscreen, op)
|
|
default:
|
|
op := &DrawRectShaderOptions{}
|
|
op.Images[0] = g.offscreen
|
|
op.GeoM = geoM
|
|
w, h := g.offscreen.Bounds().Dx(), g.offscreen.Bounds().Dy()
|
|
g.screen.DrawRectShader(w, h, g.screenShader, op)
|
|
}
|
|
}
|