internal/restorable: use a special shader to clear an image

This reduces one extra internal texture.
This commit is contained in:
Hajime Hoshi 2023-03-01 15:38:52 +09:00
parent c76201090a
commit e5bb89aa35
3 changed files with 29 additions and 60 deletions

View File

@ -125,38 +125,6 @@ type Image struct {
staleRegions []image.Rectangle staleRegions []image.Rectangle
imageType ImageType imageType ImageType
// priority indicates whether the image is restored in high priority when context-lost happens.
priority bool
}
var whiteImage *Image
func ensureWhiteImage() *Image {
if whiteImage != nil {
return whiteImage
}
// Initialize the white image lazily. Some functions like needsRestoring might not work at the initial phase.
// w and h are the white image's size. They indicate the 1x1 image with 1px padding around.
const w, h = 3, 3
whiteImage = &Image{
image: graphicscommand.NewImage(w, h, false),
width: w,
height: h,
priority: true,
}
pix := make([]byte, 4*w*h)
for i := range pix {
pix[i] = 0xff
}
// As whiteImage is the source at clearImage, initialize this with WritePixels, not clearImage.
// This operation is also important when restoring whiteImage.
whiteImage.WritePixels(pix, 0, 0, w, h)
theImages.add(whiteImage)
return whiteImage
} }
// NewImage creates a white image with the given size. // NewImage creates a white image with the given size.
@ -211,6 +179,14 @@ func (i *Image) Extend(width, height int) *Image {
// quadVertices returns vertices to render a quad. These values are passed to graphicscommand.Image. // quadVertices returns vertices to render a quad. These values are passed to graphicscommand.Image.
func quadVertices(src *Image, dx0, dy0, dx1, dy1, sx0, sy0, sx1, sy1, cr, cg, cb, ca float32) []float32 { func quadVertices(src *Image, dx0, dy0, dx1, dy1, sx0, sy0, sx1, sy1, cr, cg, cb, ca float32) []float32 {
if src == nil {
return []float32{
dx0, dy0, 0, 0, cr, cg, cb, ca,
dx1, dy0, 0, 0, cr, cg, cb, ca,
dx0, dy1, 0, 0, cr, cg, cb, ca,
dx1, dy1, 0, 0, cr, cg, cb, ca,
}
}
sw, sh := src.InternalSize() sw, sh := src.InternalSize()
swf, shf := float32(sw), float32(sh) swf, shf := float32(sw), float32(sh)
return []float32{ return []float32{
@ -222,19 +198,11 @@ func quadVertices(src *Image, dx0, dy0, dx1, dy1, sx0, sy0, sx1, sy1, cr, cg, cb
} }
func clearImage(i *graphicscommand.Image) { func clearImage(i *graphicscommand.Image) {
whiteImage := ensureWhiteImage()
if i == whiteImage.image {
panic("restorable: fillImage cannot be called on whiteImage")
}
// This needs to use 'InternalSize' to render the whole region, or edges are unexpectedly cleared on some // This needs to use 'InternalSize' to render the whole region, or edges are unexpectedly cleared on some
// devices. // devices.
dw, dh := i.InternalSize() dw, dh := i.InternalSize()
sw, sh := whiteImage.width, whiteImage.height vs := quadVertices(nil, 0, 0, float32(dw), float32(dh), 0, 0, 0, 0, 0, 0, 0, 0)
vs := quadVertices(whiteImage, 0, 0, float32(dw), float32(dh), 1, 1, float32(sw-1), float32(sh-1), 0, 0, 0, 0)
is := graphics.QuadIndices() is := graphics.QuadIndices()
srcs := [graphics.ShaderImageCount]*graphicscommand.Image{whiteImage.image}
var offsets [graphics.ShaderImageCount - 1][2]float32 var offsets [graphics.ShaderImageCount - 1][2]float32
dstRegion := graphicsdriver.Region{ dstRegion := graphicsdriver.Region{
X: 0, X: 0,
@ -242,7 +210,7 @@ func clearImage(i *graphicscommand.Image) {
Width: float32(dw), Width: float32(dw),
Height: float32(dh), Height: float32(dh),
} }
i.DrawTriangles(srcs, offsets, vs, is, graphicsdriver.BlendClear, dstRegion, graphicsdriver.Region{}, NearestFilterShader.shader, nil, false) i.DrawTriangles([graphics.ShaderImageCount]*graphicscommand.Image{}, offsets, vs, is, graphicsdriver.BlendClear, dstRegion, graphicsdriver.Region{}, clearShader.shader, nil, false)
} }
// BasePixelsForTesting returns the image's basePixels for testing. // BasePixelsForTesting returns the image's basePixels for testing.
@ -364,9 +332,6 @@ func (i *Image) WritePixels(pixels []byte, x, y, width, height int) {
// 6: Color B // 6: Color B
// 7: Color Y // 7: Color Y
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, offsets [graphics.ShaderImageCount - 1][2]float32, vertices []float32, indices []uint16, blend graphicsdriver.Blend, dstRegion, srcRegion graphicsdriver.Region, shader *Shader, uniforms []uint32, evenOdd bool) { func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, offsets [graphics.ShaderImageCount - 1][2]float32, vertices []float32, indices []uint16, blend graphicsdriver.Blend, dstRegion, srcRegion graphicsdriver.Region, shader *Shader, uniforms []uint32, evenOdd bool) {
if i.priority {
panic("restorable: DrawTriangles cannot be called on a priority image")
}
if len(vertices) == 0 { if len(vertices) == 0 {
return return
} }
@ -606,11 +571,8 @@ func (i *Image) restore(graphicsDriver graphicsdriver.Graphics) error {
gimg := graphicscommand.NewImage(w, h, false) gimg := graphicscommand.NewImage(w, h, false)
// Clear the image explicitly. // Clear the image explicitly.
if i != ensureWhiteImage() { clearImage(gimg)
// As clearImage uses whiteImage, clearImage cannot be called on whiteImage.
// It is OK to skip this since whiteImage has its entire pixel information.
clearImage(gimg)
}
i.basePixels.Apply(gimg) i.basePixels.Apply(gimg)
for _, c := range i.drawTrianglesHistory { for _, c := range i.drawTrianglesHistory {

View File

@ -222,9 +222,7 @@ func (i *images) restore(graphicsDriver graphicsdriver.Graphics) error {
} }
images := map[*Image]struct{}{} images := map[*Image]struct{}{}
for i := range i.images { for i := range i.images {
if !i.priority { images[i] = struct{}{}
images[i] = struct{}{}
}
} }
edges := map[edge]struct{}{} edges := map[edge]struct{}{}
for t := range images { for t := range images {
@ -233,12 +231,7 @@ func (i *images) restore(graphicsDriver graphicsdriver.Graphics) error {
} }
} }
sorted := []*Image{} var sorted []*Image
for i := range i.images {
if i.priority {
sorted = append(sorted, i)
}
}
for len(images) > 0 { for len(images) > 0 {
// current represents images that have no incoming edges. // current represents images that have no incoming edges.
current := map[*Image]struct{}{} current := map[*Image]struct{}{}

View File

@ -53,11 +53,12 @@ func (s *Shader) restore() {
var ( var (
NearestFilterShader *Shader NearestFilterShader *Shader
LinearFilterShader *Shader LinearFilterShader *Shader
clearShader *Shader
) )
func init() { func init() {
var wg errgroup.Group var wg errgroup.Group
var nearestIR, linearIR *shaderir.Program var nearestIR, linearIR, clearIR *shaderir.Program
wg.Go(func() error { wg.Go(func() error {
ir, err := graphics.CompileShader([]byte(builtinshader.Shader(builtinshader.FilterNearest, builtinshader.AddressUnsafe, false))) ir, err := graphics.CompileShader([]byte(builtinshader.Shader(builtinshader.FilterNearest, builtinshader.AddressUnsafe, false)))
if err != nil { if err != nil {
@ -74,9 +75,22 @@ func init() {
linearIR = ir linearIR = ir
return nil return nil
}) })
wg.Go(func() error {
ir, err := graphics.CompileShader([]byte(`package main
func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
return vec4(0)
}`))
if err != nil {
return fmt.Errorf("restorable: compiling the clear shader failed: %w", err)
}
clearIR = ir
return nil
})
if err := wg.Wait(); err != nil { if err := wg.Wait(); err != nil {
panic(err) panic(err)
} }
NearestFilterShader = NewShader(nearestIR) NearestFilterShader = NewShader(nearestIR)
LinearFilterShader = NewShader(linearIR) LinearFilterShader = NewShader(linearIR)
clearShader = NewShader(clearIR)
} }