forked from Foxyz/sync_replay_project
Shaders: Anime4K at full precision on OpenGL ES; sharp video renders the fitted picture
Colours and grid: media_kit renders through OpenGL ES, where mpv's mediump samplers made Anime4K's networks compute at half precision (fp16 on NVIDIA); the VL networks of HQ overflow and round there, strongest once the upscalers run at screen size. The extracted copies route every network read through a highp identity function; on GLES the result now equals desktop OpenGL's. Resize: the output is the picture fitted in the view (no bars, centred on whole pixels), so Flutter scales the last texture to the new view without the shrink-then-jump; the resize wait is 120 ms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4c8ddeb034
commit
ad53b6822a
8 files changed
+184
-22
No files matched your search
@@ -103,10 +103,11 @@ void main() {
|
||||
test('copies the bundle once per version, redoes a broken copy', () async {
|
||||
final support = _fresh('shaders_support');
|
||||
final dir = await extractAnime4k(support);
|
||||
expect(dir.path, endsWith('shaders/anime4k-$anime4kVersion'));
|
||||
expect(dir.path, endsWith('shaders/anime4k-$anime4kVersion-$anime4kCopyRevision'));
|
||||
for (final f in [...anime4kAllFiles, 'LICENSE']) {
|
||||
expect(File('${dir.path}/$f').readAsBytesSync(),
|
||||
File('$anime4kAssetDir/$f').readAsBytesSync(),
|
||||
final shipped = File('$anime4kAssetDir/$f');
|
||||
expect(File('${dir.path}/$f').readAsStringSync(),
|
||||
f.endsWith('.glsl') ? highpAnime4k(shipped.readAsStringSync()) : shipped.readAsStringSync(),
|
||||
reason: f);
|
||||
}
|
||||
// Second run is a no-op: a marker-tagged copy stays untouched.
|
||||
@@ -132,6 +133,36 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
test('Anime4K reads go through a highp helper, once per pass', () {
|
||||
const src = '''// header
|
||||
//!DESC pass 1
|
||||
//!HOOK MAIN
|
||||
//!BIND MAIN
|
||||
#define go_0(x_off, y_off) (MAIN_texOff(vec2(x_off, y_off)))
|
||||
vec4 hook() { return mat4(1.0) * go_0(0.0, 0.0); }
|
||||
//!DESC pass 2
|
||||
//!HOOK MAIN
|
||||
//!BIND conv2d_tf
|
||||
#define go_0(x_off, y_off) (max((conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
|
||||
#define go_1(x_off, y_off) (max(-(conv2d_tf_texOff(vec2(x_off, y_off))), 0.0))
|
||||
#define g_0 (max((conv2d_tf_tex(conv2d_tf_pos)), 0.0))
|
||||
vec4 hook() { return go_0(0.0, 0.0) + go_1(0.0, 0.0) + g_0 + MAIN_tex(MAIN_pos); }
|
||||
''';
|
||||
final out = highpAnime4k(src);
|
||||
expect('highp vec4 starsync_hp('.allMatches(out), hasLength(2), reason: 'one helper per pass');
|
||||
expect(out, contains('#define go_0(x_off, y_off) (starsync_hp(MAIN_texOff(vec2(x_off, y_off))))'));
|
||||
expect(out, contains('(max(-(starsync_hp(conv2d_tf_texOff(vec2(x_off, y_off)))), 0.0))'));
|
||||
expect(out, contains('#define g_0 (max((starsync_hp(conv2d_tf_tex(conv2d_tf_pos))), 0.0))'));
|
||||
expect(out, contains('+ g_0 + MAIN_tex(MAIN_pos); }'), reason: 'other code stays as it is');
|
||||
// Every shipped file: every network read wrapped, the helper before its first use.
|
||||
for (final f in anime4kAllFiles) {
|
||||
final o = highpAnime4k(File('$anime4kAssetDir/$f').readAsStringSync());
|
||||
for (final line in o.split('\n').where((l) => RegExp(r'^#define g(o)?_\d').hasMatch(l))) {
|
||||
expect(line, contains('starsync_hp('), reason: '$f: $line');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('shader settings persist across a reload', () async {
|
||||
Hive.init(_fresh('shaders_hive').path);
|
||||
final a = AppSettings();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:starsync/src/platform/ports/player_host.dart' show RenderMode;
|
||||
import 'package:starsync/src/platform/ports/player_host.dart' show RenderMode, fitOutputSize;
|
||||
import 'package:starsync/src/screens/home/ui.dart' show devToolsFor;
|
||||
import 'package:starsync/src/shell/video_view.dart';
|
||||
|
||||
@@ -23,6 +23,27 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('fitOutputSize', () {
|
||||
test('the picture fitted in the view, no bars', () {
|
||||
expect(fitOutputSize((1920, 1080), (1280, 720)), (1920, 1080));
|
||||
// 16:9 on a 2560x1080 screen: pillarboxed, full height.
|
||||
expect(fitOutputSize((2560, 1080), (1280, 720)), (1920, 1080));
|
||||
// The chat panel narrows the view: letterboxed, full width.
|
||||
expect(fitOutputSize((880, 720), (1280, 720)), (880, 494));
|
||||
expect(fitOutputSize((1280, 720), null), (1280, 720));
|
||||
expect(fitOutputSize((1280, 720), (0, 0)), (1280, 720));
|
||||
});
|
||||
|
||||
test('the bars split evenly: the picture sits on whole pixels', () {
|
||||
for (final (view, video) in [((881, 720), (1280, 720)), ((1000, 999), (1920, 800)), ((1366, 768), (720, 576))]) {
|
||||
final (w, h) = fitOutputSize(view, video);
|
||||
expect(w <= view.$1 && h <= view.$2, isTrue);
|
||||
expect((view.$1 - w).isEven && (view.$2 - h).isEven, isTrue, reason: '$view $video -> ($w, $h)');
|
||||
expect(w == view.$1 || h == view.$2, isTrue, reason: 'one side fills the view');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
group('subtitleLiftPos', () {
|
||||
test('a picture filling the view lifts by the controls', () {
|
||||
// 108 px of controls + 8 px gap in a 1080 px picture: 10.74 %.
|
||||
|
||||
Reference in new issue
Block a user