merge: idle-outputs (skip redraws for content on other outputs)
4 files · +56 -7
M internal/adapters/drm/output.go +2 -1
| @@ -183,7 +183,8 @@ func (o *Output) Run(ctx context.Context, newRenderer func(w, h int) (ports.Rend | |||
| 183 | 183 | } else { | |
| 184 | 184 | surfaces[c.ID] = c | |
| 185 | 185 | } | |
| 186 | - | dirty = true | |
| 186 | + | // Windows on other outputs or workspaces do not need a frame. | |
| 187 | + | dirty = dirty || scene.Shows(c.ID) | |
| 187 | 188 | case <-stats.C: | |
| 188 | 189 | ev := o.log.Info().Int("frames", frame).Int("flips", o.flips) | |
| 189 | 190 | if o.cursor != nil { | |
| @@ -183,7 +183,8 @@ func (o *Output) Run(ctx context.Context, newRenderer func(w, h int) (ports.Rend | |||||
| 183 | } else { | 183 | } else { | ||
| 184 | surfaces[c.ID] = c | 184 | surfaces[c.ID] = c | ||
| 185 | } | 185 | } | ||
| 186 | - | dirty = true | 186 | + | // Windows on other outputs or workspaces do not need a frame. |
| 187 | + | dirty = dirty || scene.Shows(c.ID) | |||
| 187 | case <-stats.C: | 188 | case <-stats.C: | ||
| 188 | ev := o.log.Info().Int("frames", frame).Int("flips", o.flips) | 189 | ev := o.log.Info().Int("frames", frame).Int("flips", o.flips) | ||
| 189 | if o.cursor != nil { | 190 | if o.cursor != nil { | ||
M internal/adapters/headless/output.go +6 -4
| @@ -32,10 +32,11 @@ func Run(ctx context.Context, opts Options, scenes <-chan ports.Scene, contents | |||
| 32 | 32 | defer r.Close() | |
| 33 | 33 | surfaces := make(map[ports.WindowID]ports.SurfaceContent) | |
| 34 | 34 | var scene ports.Scene | |
| 35 | - | haveScene := false | |
| 35 | + | haveScene, dirty := false, false | |
| 36 | 36 | frame := 0 | |
| 37 | 37 | cursorScale := -1.0 // not loaded yet | |
| 38 | 38 | update := func(c ports.SurfaceContent) { | |
| 39 | + | dirty = dirty || scene.Shows(c.ID) | |
| 39 | 40 | if c.Empty() { | |
| 40 | 41 | delete(surfaces, c.ID) | |
| 41 | 42 | } else { | |
| @@ -54,7 +55,7 @@ func Run(ctx context.Context, opts Options, scenes <-chan ports.Scene, contents | |||
| 54 | 55 | scenes = nil | |
| 55 | 56 | continue | |
| 56 | 57 | } | |
| 57 | - | scene, haveScene = s, true | |
| 58 | + | scene, haveScene, dirty = s, true, true | |
| 58 | 59 | case c, ok := <-contents: | |
| 59 | 60 | if !ok { | |
| 60 | 61 | contents = nil | |
| @@ -70,7 +71,7 @@ func Run(ctx context.Context, opts Options, scenes <-chan ports.Scene, contents | |||
| 70 | 71 | if !ok { | |
| 71 | 72 | scenes = nil | |
| 72 | 73 | } else { | |
| 73 | - | scene, haveScene = s, true | |
| 74 | + | scene, haveScene, dirty = s, true, true | |
| 74 | 75 | } | |
| 75 | 76 | case c, ok := <-contents: | |
| 76 | 77 | if !ok { | |
| @@ -82,9 +83,10 @@ func Run(ctx context.Context, opts Options, scenes <-chan ports.Scene, contents | |||
| 82 | 83 | break drain | |
| 83 | 84 | } | |
| 84 | 85 | } | |
| 85 | - | if !haveScene { | |
| 86 | + | if !haveScene || !dirty { | |
| 86 | 87 | continue | |
| 87 | 88 | } | |
| 89 | + | dirty = false | |
| 88 | 90 | if opts.Cursor != nil && opts.LoadCursor != nil && scene.Scale != cursorScale { | |
| 89 | 91 | cursorScale = scene.Scale | |
| 90 | 92 | if img, err := opts.LoadCursor(scene.Scale, 256); err == nil { | |
| @@ -32,10 +32,11 @@ func Run(ctx context.Context, opts Options, scenes <-chan ports.Scene, contents | |||||
| 32 | defer r.Close() | 32 | defer r.Close() | ||
| 33 | surfaces := make(map[ports.WindowID]ports.SurfaceContent) | 33 | surfaces := make(map[ports.WindowID]ports.SurfaceContent) | ||
| 34 | var scene ports.Scene | 34 | var scene ports.Scene | ||
| 35 | - | haveScene := false | 35 | + | haveScene, dirty := false, false |
| 36 | frame := 0 | 36 | frame := 0 | ||
| 37 | cursorScale := -1.0 // not loaded yet | 37 | cursorScale := -1.0 // not loaded yet | ||
| 38 | update := func(c ports.SurfaceContent) { | 38 | update := func(c ports.SurfaceContent) { | ||
| 39 | + | dirty = dirty || scene.Shows(c.ID) | |||
| 39 | if c.Empty() { | 40 | if c.Empty() { | ||
| 40 | delete(surfaces, c.ID) | 41 | delete(surfaces, c.ID) | ||
| 41 | } else { | 42 | } else { | ||
| @@ -54,7 +55,7 @@ func Run(ctx context.Context, opts Options, scenes <-chan ports.Scene, contents | |||||
| 54 | scenes = nil | 55 | scenes = nil | ||
| 55 | continue | 56 | continue | ||
| 56 | } | 57 | } | ||
| 57 | - | scene, haveScene = s, true | 58 | + | scene, haveScene, dirty = s, true, true |
| 58 | case c, ok := <-contents: | 59 | case c, ok := <-contents: | ||
| 59 | if !ok { | 60 | if !ok { | ||
| 60 | contents = nil | 61 | contents = nil | ||
| @@ -70,7 +71,7 @@ func Run(ctx context.Context, opts Options, scenes <-chan ports.Scene, contents | |||||
| 70 | if !ok { | 71 | if !ok { | ||
| 71 | scenes = nil | 72 | scenes = nil | ||
| 72 | } else { | 73 | } else { | ||
| 73 | - | scene, haveScene = s, true | 74 | + | scene, haveScene, dirty = s, true, true |
| 74 | } | 75 | } | ||
| 75 | case c, ok := <-contents: | 76 | case c, ok := <-contents: | ||
| 76 | if !ok { | 77 | if !ok { | ||
| @@ -82,9 +83,10 @@ func Run(ctx context.Context, opts Options, scenes <-chan ports.Scene, contents | |||||
| 82 | break drain | 83 | break drain | ||
| 83 | } | 84 | } | ||
| 84 | } | 85 | } | ||
| 85 | - | if !haveScene { | 86 | + | if !haveScene || !dirty { |
| 86 | continue | 87 | continue | ||
| 87 | } | 88 | } | ||
| 89 | + | dirty = false | |||
| 88 | if opts.Cursor != nil && opts.LoadCursor != nil && scene.Scale != cursorScale { | 90 | if opts.Cursor != nil && opts.LoadCursor != nil && scene.Scale != cursorScale { | ||
| 89 | cursorScale = scene.Scale | 91 | cursorScale = scene.Scale | ||
| 90 | if img, err := opts.LoadCursor(scene.Scale, 256); err == nil { | 92 | if img, err := opts.LoadCursor(scene.Scale, 256); err == nil { | ||
M internal/adapters/headless/output_test.go +32 -2
| @@ -69,8 +69,9 @@ func TestRun(t *testing.T) { | |||
| 69 | 69 | contents := make(chan ports.SurfaceContent, 8) | |
| 70 | 70 | r, f := recordingRenderer(t, nil) | |
| 71 | 71 | contents <- ports.SurfaceContent{ID: 1, SHM: &ports.SHMBuffer{Pool: 1}} | |
| 72 | - | scenes <- ports.Scene{Seq: 1} | |
| 73 | - | scenes <- ports.Scene{Seq: 2} | |
| 72 | + | shown := []ports.SceneWindow{{ID: 1}, {ID: 2}} | |
| 73 | + | scenes <- ports.Scene{Seq: 1, Windows: shown} | |
| 74 | + | scenes <- ports.Scene{Seq: 2, Windows: shown} | |
| 74 | 75 | contents <- ports.SurfaceContent{ID: 2, SHM: &ports.SHMBuffer{Pool: 2}} | |
| 75 | 76 | ctx, cancel := context.WithCancel(context.Background()) | |
| 76 | 77 | done := make(chan error, 1) | |
| @@ -105,6 +106,35 @@ func TestRun(t *testing.T) { | |||
| 105 | 106 | } | |
| 106 | 107 | } | |
| 107 | 108 | ||
| 109 | + | // Content of a window the output does not show draws no frame. | |
| 110 | + | func TestRunSkipsContentNotShown(t *testing.T) { | |
| 111 | + | scenes := make(chan ports.Scene, 1) | |
| 112 | + | contents := make(chan ports.SurfaceContent, 2) | |
| 113 | + | scenes <- ports.Scene{Seq: 1, Windows: []ports.SceneWindow{{ID: 1}, {ID: 3, Hidden: true}}} | |
| 114 | + | r, f := recordingRenderer(t, nil) | |
| 115 | + | ctx, cancel := context.WithCancel(context.Background()) | |
| 116 | + | done := make(chan error, 1) | |
| 117 | + | go func() { | |
| 118 | + | done <- Run(ctx, Options{Width: 2, Height: 2, NewRenderer: func(int, int) (ports.Renderer, error) { return r, nil }}, scenes, contents) | |
| 119 | + | }() | |
| 120 | + | waitFrames(t, f, 1) | |
| 121 | + | contents <- ports.SurfaceContent{ID: 2, SHM: &ports.SHMBuffer{Pool: 2}} | |
| 122 | + | contents <- ports.SurfaceContent{ID: 3, SHM: &ports.SHMBuffer{Pool: 3}} | |
| 123 | + | time.Sleep(50 * time.Millisecond) | |
| 124 | + | if s, _ := f.snapshot(); len(s) != 1 { | |
| 125 | + | t.Fatalf("%d frames for content not shown", len(s)) | |
| 126 | + | } | |
| 127 | + | contents <- ports.SurfaceContent{ID: 1, SHM: &ports.SHMBuffer{Pool: 1}} | |
| 128 | + | waitFrames(t, f, 2) | |
| 129 | + | if _, c := f.snapshot(); c[1][2].SHM == nil { | |
| 130 | + | t.Error("content not shown was dropped") | |
| 131 | + | } | |
| 132 | + | cancel() | |
| 133 | + | if err := <-done; err != nil { | |
| 134 | + | t.Fatal(err) | |
| 135 | + | } | |
| 136 | + | } | |
| 137 | + | ||
| 108 | 138 | func TestErrors(t *testing.T) { | |
| 109 | 139 | expected := errors.New("failure") | |
| 110 | 140 | scenes := make(chan ports.Scene, 1) | |
| @@ -69,8 +69,9 @@ func TestRun(t *testing.T) { | |||||
| 69 | contents := make(chan ports.SurfaceContent, 8) | 69 | contents := make(chan ports.SurfaceContent, 8) | ||
| 70 | r, f := recordingRenderer(t, nil) | 70 | r, f := recordingRenderer(t, nil) | ||
| 71 | contents <- ports.SurfaceContent{ID: 1, SHM: &ports.SHMBuffer{Pool: 1}} | 71 | contents <- ports.SurfaceContent{ID: 1, SHM: &ports.SHMBuffer{Pool: 1}} | ||
| 72 | - | scenes <- ports.Scene{Seq: 1} | 72 | + | shown := []ports.SceneWindow{{ID: 1}, {ID: 2}} |
| 73 | - | scenes <- ports.Scene{Seq: 2} | 73 | + | scenes <- ports.Scene{Seq: 1, Windows: shown} |
| 74 | + | scenes <- ports.Scene{Seq: 2, Windows: shown} | |||
| 74 | contents <- ports.SurfaceContent{ID: 2, SHM: &ports.SHMBuffer{Pool: 2}} | 75 | contents <- ports.SurfaceContent{ID: 2, SHM: &ports.SHMBuffer{Pool: 2}} | ||
| 75 | ctx, cancel := context.WithCancel(context.Background()) | 76 | ctx, cancel := context.WithCancel(context.Background()) | ||
| 76 | done := make(chan error, 1) | 77 | done := make(chan error, 1) | ||
| @@ -105,6 +106,35 @@ func TestRun(t *testing.T) { | |||||
| 105 | } | 106 | } | ||
| 106 | } | 107 | } | ||
| 107 | 108 | ||||
| 109 | + | // Content of a window the output does not show draws no frame. | |||
| 110 | + | func TestRunSkipsContentNotShown(t *testing.T) { | |||
| 111 | + | scenes := make(chan ports.Scene, 1) | |||
| 112 | + | contents := make(chan ports.SurfaceContent, 2) | |||
| 113 | + | scenes <- ports.Scene{Seq: 1, Windows: []ports.SceneWindow{{ID: 1}, {ID: 3, Hidden: true}}} | |||
| 114 | + | r, f := recordingRenderer(t, nil) | |||
| 115 | + | ctx, cancel := context.WithCancel(context.Background()) | |||
| 116 | + | done := make(chan error, 1) | |||
| 117 | + | go func() { | |||
| 118 | + | done <- Run(ctx, Options{Width: 2, Height: 2, NewRenderer: func(int, int) (ports.Renderer, error) { return r, nil }}, scenes, contents) | |||
| 119 | + | }() | |||
| 120 | + | waitFrames(t, f, 1) | |||
| 121 | + | contents <- ports.SurfaceContent{ID: 2, SHM: &ports.SHMBuffer{Pool: 2}} | |||
| 122 | + | contents <- ports.SurfaceContent{ID: 3, SHM: &ports.SHMBuffer{Pool: 3}} | |||
| 123 | + | time.Sleep(50 * time.Millisecond) | |||
| 124 | + | if s, _ := f.snapshot(); len(s) != 1 { | |||
| 125 | + | t.Fatalf("%d frames for content not shown", len(s)) | |||
| 126 | + | } | |||
| 127 | + | contents <- ports.SurfaceContent{ID: 1, SHM: &ports.SHMBuffer{Pool: 1}} | |||
| 128 | + | waitFrames(t, f, 2) | |||
| 129 | + | if _, c := f.snapshot(); c[1][2].SHM == nil { | |||
| 130 | + | t.Error("content not shown was dropped") | |||
| 131 | + | } | |||
| 132 | + | cancel() | |||
| 133 | + | if err := <-done; err != nil { | |||
| 134 | + | t.Fatal(err) | |||
| 135 | + | } | |||
| 136 | + | } | |||
| 137 | + | ||||
| 108 | func TestErrors(t *testing.T) { | 138 | func TestErrors(t *testing.T) { | ||
| 109 | expected := errors.New("failure") | 139 | expected := errors.New("failure") | ||
| 110 | scenes := make(chan ports.Scene, 1) | 140 | scenes := make(chan ports.Scene, 1) | ||
M internal/ports/messages.go +16 -0
| @@ -327,6 +327,22 @@ type Scene struct { | |||
| 327 | 327 | Layers []SceneLayer | |
| 328 | 328 | } | |
| 329 | 329 | ||
| 330 | + | // Shows reports whether the scene draws the surface of id: only its | |
| 331 | + | // content changes need a new frame. | |
| 332 | + | func (s Scene) Shows(id WindowID) bool { | |
| 333 | + | for _, w := range s.Windows { | |
| 334 | + | if w.ID == id && !w.Hidden { | |
| 335 | + | return true | |
| 336 | + | } | |
| 337 | + | } | |
| 338 | + | for _, l := range s.Layers { | |
| 339 | + | if l.ID == id { | |
| 340 | + | return true | |
| 341 | + | } | |
| 342 | + | } | |
| 343 | + | return false | |
| 344 | + | } | |
| 345 | + | ||
| 330 | 346 | // Border carries the window border style; colors are #rrggbb, "" skips drawing. | |
| 331 | 347 | type Border struct { | |
| 332 | 348 | Width int | |
| @@ -327,6 +327,22 @@ type Scene struct { | |||||
| 327 | Layers []SceneLayer | 327 | Layers []SceneLayer | ||
| 328 | } | 328 | } | ||
| 329 | 329 | ||||
| 330 | + | // Shows reports whether the scene draws the surface of id: only its | |||
| 331 | + | // content changes need a new frame. | |||
| 332 | + | func (s Scene) Shows(id WindowID) bool { | |||
| 333 | + | for _, w := range s.Windows { | |||
| 334 | + | if w.ID == id && !w.Hidden { | |||
| 335 | + | return true | |||
| 336 | + | } | |||
| 337 | + | } | |||
| 338 | + | for _, l := range s.Layers { | |||
| 339 | + | if l.ID == id { | |||
| 340 | + | return true | |||
| 341 | + | } | |||
| 342 | + | } | |||
| 343 | + | return false | |||
| 344 | + | } | |||
| 345 | + | ||||
| 330 | // Border carries the window border style; colors are #rrggbb, "" skips drawing. | 346 | // Border carries the window border style; colors are #rrggbb, "" skips drawing. | ||
| 331 | type Border struct { | 347 | type Border struct { | ||
| 332 | Width int | 348 | Width int | ||