aboutsummaryrefslogtreecommitdiff
path: root/tools/skqp/jitter_gms.cpp
blob: 96804712feb3c95dc8f6d5227854d376533d0ca4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// Copyright 2018 Google LLC.
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

// Jitter GMs
//
// Re-execute rendering tests with slight translational changes and see if
// there is a significant change.  Print `1` if the named test has no
// significant change, `0` otherwise

#include "gm.h"
#include "SkGraphics.h"
#include "SkExecutor.h"
#include "SkSemaphore.h"

#include "skqp_model.h"

#include <algorithm>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <mutex>
#include <string>
#include <vector>

// Error tolerance distance in 8888 color space with Manhattan metric on color channel.
static constexpr uint8_t kSkiaSkqpGlobalErrorTolerance = 8;

// Number of times to jitter the canvas.
static constexpr int kNumberOfJitters = 7;

// Distance to translate the canvas in each jitter (direction will be different each time).
static constexpr float kJitterMagnitude = 0.03125f;

// The `kNumberOfJitters` different runs will each go in a different direction.
// this is the angle (in radians) for the first one.
static constexpr float kPhase = 0.3f;

static void do_gm(SkBitmap* bm, skiagm::GM* gm, SkPoint jitter) {
    SkASSERT(bm);
    SkASSERT(gm);
    SkASSERT(bm->dimensions() == gm->getISize());
    SkCanvas canvas(*bm);
    SkAutoCanvasRestore autoCanvasRestore(&canvas, true);
    canvas.clear(SK_ColorWHITE);
    canvas.translate(jitter.x(), jitter.y());
    gm->draw(&canvas);
    canvas.flush();
}

// Return true if passes jitter test.
static bool test_jitter(skiagm::GM* gm) {
    SkASSERT(gm);
    SkISize size = gm->getISize();
    SkBitmap control, experimental;
    control.allocN32Pixels(size.width(), size.height());
    experimental.allocN32Pixels(size.width(), size.height());
    do_gm(&control, gm, {0, 0});
    for (int i = 0; i < kNumberOfJitters; ++i) {
        float angle = i * (6.2831853f / kNumberOfJitters) + kPhase;
        do_gm(&experimental, gm, SkPoint{kJitterMagnitude * cosf(angle),
                                         kJitterMagnitude * sinf(angle)});
        SkQP::RenderOutcome result = skqp::Check(
                control.pixmap(), control.pixmap(), experimental.pixmap(),
                kSkiaSkqpGlobalErrorTolerance, nullptr);
        if (result.fTotalError > 0) {
            return false;
        }
    }
    return true;
}

static bool do_this_test(const char* name,
                    const std::vector<std::string>& doNotRun,
                    const std::vector<std::string>& testOnlyThese) {
    for (const std::string& bad : doNotRun) {
        if (bad == name) {
            return false;
        }
    }
    for (const std::string& good : testOnlyThese) {
        if (good == name) {
            return true;
        }
    }
    return testOnlyThese.empty();
}


int main(int argc, char** argv) {
    std::vector<std::string> doNotRun;
    std::vector<std::string> testOnlyThese;
    if (argc > 1) {
        std::ifstream ifs(argv[1]);
        if (ifs.is_open()) {
            std::string str;
            while (std::getline(ifs, str)) {
                doNotRun.push_back(str);
            }
        }
    }
    if (argc > 2) {
        for (int i = 2; i < argc; ++i) {
            testOnlyThese.emplace_back(argv[i]);
        }
    }
    SkGraphics::Init();
    std::mutex mutex;
    std::vector<std::string> goodResults;
    std::vector<std::string> badResults;

    int total = 0;
    SkSemaphore semaphore;
    auto executor = SkExecutor::MakeFIFOThreadPool();
    for (skiagm::GMFactory factory : skiagm::GMRegistry::Range()) {
        ++total;
        executor->add([factory, &mutex, &goodResults, &badResults,
                       &semaphore, &doNotRun, &testOnlyThese](){
            std::unique_ptr<skiagm::GM> gm(factory(nullptr));
            const char* name = gm->getName();
            if (do_this_test(name, doNotRun, testOnlyThese)) {
                bool success = test_jitter(gm.get());
                std::lock_guard<std::mutex> lock(mutex);
                if (success) {
                    goodResults.emplace_back(name);
                } else {
                    badResults.emplace_back(name);
                }
                fputc('.', stderr);
                fflush(stderr);
            }
            semaphore.signal();
        });
    }
    while (total-- > 0) { semaphore.wait(); }
    fputc('\n', stderr);
    fflush(stderr);
    std::sort(goodResults.begin(), goodResults.end());
    std::sort(badResults.begin(), badResults.end());
    std::ofstream good("good.txt");
    std::ofstream bad("bad.txt");
    for (const std::string& s : goodResults) { good << s << '\n'; }
    for (const std::string& s : badResults) { bad << s << '\n'; }
    fprintf(stderr, "good = %u\nbad = %u\n\n",
            (unsigned)goodResults.size(), (unsigned)badResults.size());
}