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:
Hajime Hoshi 2022-09-06 18:21:33 +09:00
parent 62127e432e
commit bd43b42ee5
4 changed files with 84 additions and 18 deletions

View File

@ -1407,7 +1407,7 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcs [graphics.Sh
} else {
if evenOdd {
s, err := shader.pipelineState(mode, prepareStencil)
s, err := shader.pipelineState(mode, prepareStencil, dst.screen)
if err != nil {
return err
}
@ -1415,7 +1415,7 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcs [graphics.Sh
return err
}
s, err = shader.pipelineState(mode, drawWithStencil)
s, err = shader.pipelineState(mode, drawWithStencil, dst.screen)
if err != nil {
return err
}
@ -1423,7 +1423,7 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcs [graphics.Sh
return err
}
} else {
s, err := shader.pipelineState(mode, noStencil)
s, err := shader.pipelineState(mode, noStencil, dst.screen)
if err != nil {
return err
}
@ -1866,6 +1866,7 @@ const (
type pipelineStateKey struct {
compositeMode graphicsdriver.CompositeMode
stencilMode stencilMode
screen bool
}
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{
compositeMode: compositeMode,
stencilMode: stencilMode,
screen: screen,
}
if state, ok := s.pipelineStates[key]; ok {
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 {
return nil, err
}

View File

@ -924,7 +924,7 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.
noStencil,
} {
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 {
return err
}

View File

@ -26,6 +26,7 @@ import (
type shaderRpsKey struct {
compositeMode graphicsdriver.CompositeMode
stencilMode stencilMode
screen bool
}
type Shader struct {
@ -85,11 +86,13 @@ func (s *Shader) init(device mtl.Device) error {
return nil
}
func (s *Shader) RenderPipelineState(device mtl.Device, compositeMode graphicsdriver.CompositeMode, stencilMode stencilMode) (mtl.RenderPipelineState, error) {
if rps, ok := s.rpss[shaderRpsKey{
func (s *Shader) RenderPipelineState(view view, compositeMode graphicsdriver.CompositeMode, stencilMode stencilMode, screen bool) (mtl.RenderPipelineState, error) {
key := shaderRpsKey{
compositeMode: compositeMode,
stencilMode: stencilMode,
}]; ok {
screen: screen,
}
if rps, ok := s.rpss[key]; ok {
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.
rpld.ColorAttachments[0].PixelFormat = mtl.PixelFormatRGBA8UNorm
pix := mtl.PixelFormatRGBA8UNorm
if screen {
pix = view.colorPixelFormat()
}
rpld.ColorAttachments[0].PixelFormat = pix
rpld.ColorAttachments[0].BlendingEnabled = true
src, dst := compositeMode.Operations()
@ -116,14 +123,11 @@ func (s *Shader) RenderPipelineState(device mtl.Device, compositeMode graphicsdr
rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskAll
}
rps, err := device.MakeRenderPipelineState(rpld)
rps, err := view.getMTLDevice().MakeRenderPipelineState(rpld)
if err != nil {
return mtl.RenderPipelineState{}, err
}
s.rpss[shaderRpsKey{
compositeMode: compositeMode,
stencilMode: stencilMode,
}] = rps
s.rpss[key] = rps
return rps, nil
}

View File

@ -30,6 +30,40 @@ import (
"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 {
NewOffscreenImage(width, height int) *Image
Layout(outsideWidth, outsideHeight int) (int, int)
@ -49,6 +83,8 @@ type context struct {
outsideWidth float64
outsideHeight float64
screenShader *Shader
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).
if w, h := c.layoutGame(outsideWidth, outsideHeight, deviceScaleFactor); w == 0 || h == 0 {
return nil
@ -186,15 +231,16 @@ func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics) {
gty += offsetY
var filter graphicsdriver.Filter
var screenFilter bool
switch {
case !theGlobalState.isScreenFilterEnabled():
filter = graphicsdriver.FilterNearest
case math.Floor(s) == s:
filter = graphicsdriver.FilterNearest
case s > 1:
filter = graphicsdriver.FilterScreen
screenFilter = true
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).
filter = graphicsdriver.FilterLinear
}
@ -213,7 +259,21 @@ func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics) {
is := graphics.QuadIndices()
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) {