[Impeller] Gpu model information to Skia gold (flutter/engine#41216)

Does two things:

- Exposes a string about the GPU model on `impeller::Context`
- passes that string on to Skia gold when we add a test image.

This should help reduce noise/flakiness in golden images.
This commit is contained in:
Dan Field 2023-04-18 13:11:05 -07:00 committed by GitHub
parent d7752e30c5
commit 842e8cea52
15 changed files with 108 additions and 26 deletions

View File

@ -5,6 +5,7 @@
#include "impeller/golden_tests/golden_digest.h"
#include <fstream>
#include <sstream>
static const double kMaxDiffPixelsPercent = 0.01;
static const int32_t kMaxColorDelta = 8;
@ -23,6 +24,13 @@ GoldenDigest* GoldenDigest::Instance() {
GoldenDigest::GoldenDigest() {}
void GoldenDigest::AddDimension(const std::string& name,
const std::string& value) {
std::stringstream ss;
ss << "\"" << value << "\"";
dimensions_[name] = ss.str();
}
void GoldenDigest::AddImage(const std::string& test_name,
const std::string& filename,
int32_t width,
@ -38,15 +46,28 @@ bool GoldenDigest::Write(WorkingDirectory* working_directory) {
return false;
}
fout << "[" << std::endl;
fout << "{" << std::endl;
fout << " \"dimensions\": {" << std::endl;
bool is_first = true;
for (const auto& dimension : dimensions_) {
if (!is_first) {
fout << "," << std::endl;
}
is_first = false;
fout << " \"" << dimension.first << "\": " << dimension.second;
}
fout << std::endl << " }," << std::endl;
fout << " \"entries\":" << std::endl;
fout << " [" << std::endl;
is_first = true;
for (const auto& entry : entries_) {
if (!is_first) {
fout << "," << std::endl;
}
is_first = false;
fout << " { "
fout << " { "
<< "\"testName\" : \"" << entry.test_name << "\", "
<< "\"filename\" : \"" << entry.filename << "\", "
<< "\"width\" : " << entry.width << ", "
@ -64,7 +85,9 @@ bool GoldenDigest::Write(WorkingDirectory* working_directory) {
fout << "\"maxColorDelta\":" << entry.max_color_delta << " ";
fout << "}";
}
fout << std::endl << "]" << std::endl;
fout << std::endl << " ]" << std::endl;
fout << "}" << std::endl;
fout.close();
return true;

View File

@ -4,6 +4,7 @@
#pragma once
#include <map>
#include <string>
#include <vector>
@ -18,6 +19,8 @@ class GoldenDigest {
public:
static GoldenDigest* Instance();
void AddDimension(const std::string& name, const std::string& value);
void AddImage(const std::string& test_name,
const std::string& filename,
int32_t width,
@ -42,6 +45,7 @@ class GoldenDigest {
static GoldenDigest* instance_;
std::vector<Entry> entries_;
std::map<std::string, std::string> dimensions_;
};
} // namespace testing
} // namespace impeller

View File

@ -78,6 +78,9 @@ void GoldenPlaygroundTest::SetUp() {
"GoldenPlaygroundTest doesn't support interactive playground tests "
"yet.");
}
testing::GoldenDigest::Instance()->AddDimension(
"gpu_string", GetContext()->DescribeGpuModel());
}
PlaygroundBackend GoldenPlaygroundTest::GetBackend() const {

View File

@ -52,6 +52,12 @@ class GoldenTests : public ::testing::Test {
MetalScreenshoter& Screenshoter() { return *screenshoter_; }
void SetUp() override {
testing::GoldenDigest::Instance()->AddDimension(
"gpu_string",
Screenshoter().GetContext().GetContext()->DescribeGpuModel());
}
private:
std::unique_ptr<MetalScreenshoter> screenshoter_;
};

View File

@ -2,9 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'dart:io';
import 'package:golden_tests_harvester/golden_tests_harvester.dart';
import 'package:path/path.dart' as p;
import 'package:process/src/interface/process_manager.dart';
import 'package:skia_gold_client/skia_gold_client.dart';
@ -14,21 +16,25 @@ bool get isLuciEnv => Platform.environment.containsKey(_kLuciEnvName);
/// Fake SkiaGoldClient that is used if the harvester is run outside of Luci.
class FakeSkiaGoldClient implements SkiaGoldClient {
FakeSkiaGoldClient(this._workingDirectory);
FakeSkiaGoldClient(this._workingDirectory, {this.dimensions});
final Directory _workingDirectory;
@override
final Map<String, String>? dimensions;
@override
Future<void> addImg(String testName, File goldenFile,
{double differentPixelsRate = 0.01,
int pixelColorDelta = 0,
required int screenshotSize}) async {
Logger.instance.log('addImg testName:$testName goldenFile:${goldenFile.path} screenshotSize:$screenshotSize differentPixelsRate:$differentPixelsRate pixelColorDelta:$pixelColorDelta');
Logger.instance.log(
'addImg testName:$testName goldenFile:${goldenFile.path} screenshotSize:$screenshotSize differentPixelsRate:$differentPixelsRate pixelColorDelta:$pixelColorDelta');
}
@override
Future<void> auth() async {
Logger.instance.log('auth');
Logger.instance.log('auth dimensions:${dimensions ?? 'null'}');
}
@override
@ -36,9 +42,6 @@ class FakeSkiaGoldClient implements SkiaGoldClient {
throw UnimplementedError();
}
@override
Map<String, String>? get dimensions => throw UnimplementedError();
@override
List<String> getCIArguments() {
throw UnimplementedError();
@ -80,9 +83,22 @@ Future<void> main(List<String> arguments) async {
}
final Directory workDirectory = Directory(arguments[0]);
final SkiaGoldClient skiaGoldClient = isLuciEnv
? SkiaGoldClient(workDirectory)
: FakeSkiaGoldClient(workDirectory);
await harvest(skiaGoldClient, workDirectory);
final File digest = File(p.join(workDirectory.path, 'digest.json'));
if (!digest.existsSync()) {
Logger.instance
.log('Error: digest.json does not exist in ${workDirectory.path}.');
return;
}
final Object? decoded = jsonDecode(digest.readAsStringSync());
final Map<String?, Object?> data = (decoded as Map<String?, Object?>?)!;
final Map<String, String> dimensions =
(data['dimensions'] as Map<String, Object?>?)!.cast<String, String>();
final List<Object?> entries = (data['entries'] as List<Object?>?)!;
final SkiaGoldClient skiaGoldClient = isLuciEnv
? SkiaGoldClient(workDirectory, dimensions: dimensions)
: FakeSkiaGoldClient(workDirectory, dimensions: dimensions);
await harvest(skiaGoldClient, workDirectory, entries);
}

View File

@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as p;
@ -14,30 +13,25 @@ export 'logger.dart';
/// Reads the digest inside of [workDirectory], sending tests to
/// [skiaGoldClient].
Future<void> harvest(
SkiaGoldClient skiaGoldClient, Directory workDirectory) async {
Future<void> harvest(SkiaGoldClient skiaGoldClient, Directory workDirectory,
List<Object?> entries) async {
await skiaGoldClient.auth();
final File digest = File(p.join(workDirectory.path, 'digest.json'));
if (!digest.existsSync()) {
Logger.instance
.log('Error: digest.json does not exist in ${workDirectory.path}.');
return;
}
final Object? decoded = jsonDecode(digest.readAsStringSync());
final List<Object?> entries = (decoded as List<Object?>?)!;
final List<Future<void>> pendingComparisons = <Future<void>>[];
for (final Object? entry in entries) {
final Map<String, Object?> map = (entry as Map<String, Object?>?)!;
final String filename = (map['filename'] as String?)!;
final int width = (map['width'] as int?)!;
final int height = (map['height'] as int?)!;
final double maxDiffPixelsPercent = (map['maxDiffPixelsPercent'] as double?)!;
final double maxDiffPixelsPercent =
(map['maxDiffPixelsPercent'] as double?)!;
final int maxColorDelta = (map['maxColorDelta'] as int?)!;
final File goldenImage = File(p.join(workDirectory.path, filename));
final Future<void> future = skiaGoldClient
.addImg(filename, goldenImage,
screenshotSize: width * height, differentPixelsRate: maxDiffPixelsPercent, pixelColorDelta: maxColorDelta)
screenshotSize: width * height,
differentPixelsRate: maxDiffPixelsPercent,
pixelColorDelta: maxColorDelta)
.catchError((dynamic err) {
Logger.instance.log('skia gold comparison failed: $err');
throw Exception('Failed comparison: $filename');

View File

@ -104,6 +104,11 @@ bool ContextGLES::IsValid() const {
return is_valid_;
}
// |Context|
std::string ContextGLES::DescribeGpuModel() const {
return reactor_->GetProcTable().GetDescription()->GetString();
}
// |Context|
std::shared_ptr<Allocator> ContextGLES::GetResourceAllocator() const {
return resource_allocator_;

View File

@ -47,6 +47,9 @@ class ContextGLES final : public Context,
std::unique_ptr<ProcTableGLES> gl,
const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries);
// |Context|
std::string DescribeGpuModel() const override;
// |Context|
bool IsValid() const override;

View File

@ -36,6 +36,9 @@ class ContextMTL final : public Context,
id<MTLDevice> GetMTLDevice() const;
// |Context|
std::string DescribeGpuModel() const override;
// |Context|
bool IsValid() const override;

View File

@ -225,6 +225,11 @@ std::shared_ptr<ContextMTL> ContextMTL::Create(
ContextMTL::~ContextMTL() = default;
// |Context|
std::string ContextMTL::DescribeGpuModel() const {
return std::string([[device_ name] UTF8String]);
}
// |Context|
bool ContextMTL::IsValid() const {
return is_valid_;

View File

@ -344,6 +344,10 @@ void ContextVK::Setup(Settings settings) {
return;
}
VkPhysicalDeviceProperties physical_device_properties;
dispatcher.vkGetPhysicalDeviceProperties(physical_device.value(),
&physical_device_properties);
//----------------------------------------------------------------------------
/// All done!
///
@ -358,6 +362,7 @@ void ContextVK::Setup(Settings settings) {
queues_ = std::move(queues);
device_capabilities_ = std::move(caps);
fence_waiter_ = std::move(fence_waiter);
device_name_ = std::string(physical_device_properties.deviceName);
is_valid_ = true;
//----------------------------------------------------------------------------
@ -367,6 +372,11 @@ void ContextVK::Setup(Settings settings) {
SetDebugName(device_.get(), device_.get(), "ImpellerDevice");
}
// |Context|
std::string ContextVK::DescribeGpuModel() const {
return device_name_;
}
bool ContextVK::IsValid() const {
return is_valid_;
}

View File

@ -49,6 +49,9 @@ class ContextVK final : public Context, public BackendCast<ContextVK, Context> {
// |Context|
~ContextVK() override;
// |Context|
std::string DescribeGpuModel() const override;
// |Context|
bool IsValid() const override;
@ -130,6 +133,7 @@ class ContextVK final : public Context, public BackendCast<ContextVK, Context> {
std::shared_ptr<SwapchainVK> swapchain_;
std::shared_ptr<const Capabilities> device_capabilities_;
std::shared_ptr<FenceWaiterVK> fence_waiter_;
std::string device_name_;
bool is_valid_ = false;

View File

@ -23,6 +23,8 @@ class Context : public std::enable_shared_from_this<Context> {
public:
virtual ~Context();
virtual std::string DescribeGpuModel() const = 0;
virtual bool IsValid() const = 0;
virtual const std::shared_ptr<const Capabilities>& GetCapabilities()

View File

@ -85,6 +85,8 @@ class MockCommandBuffer : public CommandBuffer {
class MockImpellerContext : public Context {
public:
MOCK_CONST_METHOD0(DescribeGpuModel, std::string());
MOCK_CONST_METHOD0(IsValid, bool());
MOCK_CONST_METHOD0(GetResourceAllocator, std::shared_ptr<Allocator>());

View File

@ -113,6 +113,8 @@ class TestImpellerContext : public impeller::Context {
public:
TestImpellerContext() = default;
std::string DescribeGpuModel() const override { return "TestGpu"; }
bool IsValid() const override { return true; }
const std::shared_ptr<const Capabilities>& GetCapabilities() const override {