mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 03:08:54 +01:00
internal/ui: reland the screen shader in Kage
This change relads a part of the change to use the screen shader instead of FilterScreen, but with the issue on iOS fixed. Let's remove FilterScreen later. Updates #2282
This commit is contained in:
parent
62127e432e
commit
bd43b42ee5
@ -1407,7 +1407,7 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcs [graphics.Sh
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
if evenOdd {
|
if evenOdd {
|
||||||
s, err := shader.pipelineState(mode, prepareStencil)
|
s, err := shader.pipelineState(mode, prepareStencil, dst.screen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -1415,7 +1415,7 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcs [graphics.Sh
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err = shader.pipelineState(mode, drawWithStencil)
|
s, err = shader.pipelineState(mode, drawWithStencil, dst.screen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -1423,7 +1423,7 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcs [graphics.Sh
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
s, err := shader.pipelineState(mode, noStencil)
|
s, err := shader.pipelineState(mode, noStencil, dst.screen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -1866,6 +1866,7 @@ const (
|
|||||||
type pipelineStateKey struct {
|
type pipelineStateKey struct {
|
||||||
compositeMode graphicsdriver.CompositeMode
|
compositeMode graphicsdriver.CompositeMode
|
||||||
stencilMode stencilMode
|
stencilMode stencilMode
|
||||||
|
screen bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Shader struct {
|
type Shader struct {
|
||||||
@ -1902,16 +1903,17 @@ func (s *Shader) disposeImpl() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Shader) pipelineState(compositeMode graphicsdriver.CompositeMode, stencilMode stencilMode) (*_ID3D12PipelineState, error) {
|
func (s *Shader) pipelineState(compositeMode graphicsdriver.CompositeMode, stencilMode stencilMode, screen bool) (*_ID3D12PipelineState, error) {
|
||||||
key := pipelineStateKey{
|
key := pipelineStateKey{
|
||||||
compositeMode: compositeMode,
|
compositeMode: compositeMode,
|
||||||
stencilMode: stencilMode,
|
stencilMode: stencilMode,
|
||||||
|
screen: screen,
|
||||||
}
|
}
|
||||||
if state, ok := s.pipelineStates[key]; ok {
|
if state, ok := s.pipelineStates[key]; ok {
|
||||||
return state, nil
|
return state, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
state, err := s.graphics.pipelineStates.newPipelineState(s.graphics.device, s.vertexShader, s.pixelShader, compositeMode, stencilMode, false)
|
state, err := s.graphics.pipelineStates.newPipelineState(s.graphics.device, s.vertexShader, s.pixelShader, compositeMode, stencilMode, screen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -924,7 +924,7 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.
|
|||||||
noStencil,
|
noStencil,
|
||||||
} {
|
} {
|
||||||
var err error
|
var err error
|
||||||
rpss[stencil], err = g.shaders[shaderID].RenderPipelineState(g.view.getMTLDevice(), mode, stencil)
|
rpss[stencil], err = g.shaders[shaderID].RenderPipelineState(g.view, mode, stencil, dst.screen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
type shaderRpsKey struct {
|
type shaderRpsKey struct {
|
||||||
compositeMode graphicsdriver.CompositeMode
|
compositeMode graphicsdriver.CompositeMode
|
||||||
stencilMode stencilMode
|
stencilMode stencilMode
|
||||||
|
screen bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Shader struct {
|
type Shader struct {
|
||||||
@ -85,11 +86,13 @@ func (s *Shader) init(device mtl.Device) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Shader) RenderPipelineState(device mtl.Device, compositeMode graphicsdriver.CompositeMode, stencilMode stencilMode) (mtl.RenderPipelineState, error) {
|
func (s *Shader) RenderPipelineState(view view, compositeMode graphicsdriver.CompositeMode, stencilMode stencilMode, screen bool) (mtl.RenderPipelineState, error) {
|
||||||
if rps, ok := s.rpss[shaderRpsKey{
|
key := shaderRpsKey{
|
||||||
compositeMode: compositeMode,
|
compositeMode: compositeMode,
|
||||||
stencilMode: stencilMode,
|
stencilMode: stencilMode,
|
||||||
}]; ok {
|
screen: screen,
|
||||||
|
}
|
||||||
|
if rps, ok := s.rpss[key]; ok {
|
||||||
return rps, nil
|
return rps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,7 +105,11 @@ func (s *Shader) RenderPipelineState(device mtl.Device, compositeMode graphicsdr
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: For the precise pixel format, whether the render target is the screen or not must be considered.
|
// TODO: For the precise pixel format, whether the render target is the screen or not must be considered.
|
||||||
rpld.ColorAttachments[0].PixelFormat = mtl.PixelFormatRGBA8UNorm
|
pix := mtl.PixelFormatRGBA8UNorm
|
||||||
|
if screen {
|
||||||
|
pix = view.colorPixelFormat()
|
||||||
|
}
|
||||||
|
rpld.ColorAttachments[0].PixelFormat = pix
|
||||||
rpld.ColorAttachments[0].BlendingEnabled = true
|
rpld.ColorAttachments[0].BlendingEnabled = true
|
||||||
|
|
||||||
src, dst := compositeMode.Operations()
|
src, dst := compositeMode.Operations()
|
||||||
@ -116,14 +123,11 @@ func (s *Shader) RenderPipelineState(device mtl.Device, compositeMode graphicsdr
|
|||||||
rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskAll
|
rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskAll
|
||||||
}
|
}
|
||||||
|
|
||||||
rps, err := device.MakeRenderPipelineState(rpld)
|
rps, err := view.getMTLDevice().MakeRenderPipelineState(rpld)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mtl.RenderPipelineState{}, err
|
return mtl.RenderPipelineState{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.rpss[shaderRpsKey{
|
s.rpss[key] = rps
|
||||||
compositeMode: compositeMode,
|
|
||||||
stencilMode: stencilMode,
|
|
||||||
}] = rps
|
|
||||||
return rps, nil
|
return rps, nil
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,40 @@ import (
|
|||||||
"github.com/hajimehoshi/ebiten/v2/internal/hooks"
|
"github.com/hajimehoshi/ebiten/v2/internal/hooks"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const screenShader = `package main
|
||||||
|
|
||||||
|
var Scale vec2
|
||||||
|
|
||||||
|
func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
|
||||||
|
sourceSize := imageSrcTextureSize()
|
||||||
|
// texelSize is one pixel size in texel sizes.
|
||||||
|
texelSize := 1 / sourceSize
|
||||||
|
halfScaledTexelSize := texelSize / 2 / Scale
|
||||||
|
|
||||||
|
// Shift 1/512 [texel] to avoid the tie-breaking issue.
|
||||||
|
// As all the vertex positions are aligned to 1/16 [pixel], this shiting should work in most cases.
|
||||||
|
pos := texCoord
|
||||||
|
p0 := pos - halfScaledTexelSize + (texelSize / 512)
|
||||||
|
p1 := pos + halfScaledTexelSize + (texelSize / 512)
|
||||||
|
|
||||||
|
// 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 * sourceSize)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
type Game interface {
|
type Game interface {
|
||||||
NewOffscreenImage(width, height int) *Image
|
NewOffscreenImage(width, height int) *Image
|
||||||
Layout(outsideWidth, outsideHeight int) (int, int)
|
Layout(outsideWidth, outsideHeight int) (int, int)
|
||||||
@ -49,6 +83,8 @@ type context struct {
|
|||||||
outsideWidth float64
|
outsideWidth float64
|
||||||
outsideHeight float64
|
outsideHeight float64
|
||||||
|
|
||||||
|
screenShader *Shader
|
||||||
|
|
||||||
m sync.Mutex
|
m sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,6 +144,15 @@ func (c *context) updateFrameImpl(graphicsDriver graphicsdriver.Graphics, update
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Create a shader for the screen if necessary.
|
||||||
|
if c.screenShader == nil {
|
||||||
|
ir, err := graphics.CompileShader([]byte(screenShader))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.screenShader = NewShader(ir)
|
||||||
|
}
|
||||||
|
|
||||||
// ForceUpdate can be invoked even if the context is not initialized yet (#1591).
|
// ForceUpdate can be invoked even if the context is not initialized yet (#1591).
|
||||||
if w, h := c.layoutGame(outsideWidth, outsideHeight, deviceScaleFactor); w == 0 || h == 0 {
|
if w, h := c.layoutGame(outsideWidth, outsideHeight, deviceScaleFactor); w == 0 || h == 0 {
|
||||||
return nil
|
return nil
|
||||||
@ -186,15 +231,16 @@ func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics) {
|
|||||||
gty += offsetY
|
gty += offsetY
|
||||||
|
|
||||||
var filter graphicsdriver.Filter
|
var filter graphicsdriver.Filter
|
||||||
|
var screenFilter bool
|
||||||
switch {
|
switch {
|
||||||
case !theGlobalState.isScreenFilterEnabled():
|
case !theGlobalState.isScreenFilterEnabled():
|
||||||
filter = graphicsdriver.FilterNearest
|
filter = graphicsdriver.FilterNearest
|
||||||
case math.Floor(s) == s:
|
case math.Floor(s) == s:
|
||||||
filter = graphicsdriver.FilterNearest
|
filter = graphicsdriver.FilterNearest
|
||||||
case s > 1:
|
case s > 1:
|
||||||
filter = graphicsdriver.FilterScreen
|
screenFilter = true
|
||||||
default:
|
default:
|
||||||
// FilterScreen works with >=1 scale, but does not well with <1 scale.
|
// screenShader works with >=1 scale, but does not well with <1 scale.
|
||||||
// Use regular FilterLinear instead so far (#669).
|
// Use regular FilterLinear instead so far (#669).
|
||||||
filter = graphicsdriver.FilterLinear
|
filter = graphicsdriver.FilterLinear
|
||||||
}
|
}
|
||||||
@ -213,7 +259,21 @@ func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics) {
|
|||||||
is := graphics.QuadIndices()
|
is := graphics.QuadIndices()
|
||||||
|
|
||||||
srcs := [graphics.ShaderImageCount]*Image{c.offscreen}
|
srcs := [graphics.ShaderImageCount]*Image{c.offscreen}
|
||||||
c.screen.DrawTriangles(srcs, vs, is, affine.ColorMIdentity{}, graphicsdriver.CompositeModeCopy, filter, graphicsdriver.AddressUnsafe, dstRegion, graphicsdriver.Region{}, [graphics.ShaderImageCount - 1][2]float32{}, nil, nil, false, true)
|
|
||||||
|
var shader *Shader
|
||||||
|
var uniforms [][]float32
|
||||||
|
if screenFilter {
|
||||||
|
shader = c.screenShader
|
||||||
|
dstWidth, dstHeight := c.screen.width, c.screen.height
|
||||||
|
srcWidth, srcHeight := c.offscreen.width, c.offscreen.height
|
||||||
|
uniforms = shader.ConvertUniforms(map[string]interface{}{
|
||||||
|
"Scale": []float32{
|
||||||
|
float32(dstWidth) / float32(srcWidth),
|
||||||
|
float32(dstHeight) / float32(srcHeight),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
c.screen.DrawTriangles(srcs, vs, is, affine.ColorMIdentity{}, graphicsdriver.CompositeModeCopy, filter, graphicsdriver.AddressUnsafe, dstRegion, graphicsdriver.Region{}, [graphics.ShaderImageCount - 1][2]float32{}, shader, uniforms, false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) layoutGame(outsideWidth, outsideHeight float64, deviceScaleFactor float64) (int, int) {
|
func (c *context) layoutGame(outsideWidth, outsideHeight float64, deviceScaleFactor float64) (int, int) {
|
||||||
|
Loading…
Reference in New Issue
Block a user