1/* 2 * Copyright (c) 2013 The WebM project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11#include <string> 12 13#include "third_party/googletest/src/include/gtest/gtest.h" 14#include "./vpx_config.h" 15#include "test/codec_factory.h" 16#include "test/decode_test_driver.h" 17#include "test/md5_helper.h" 18#if CONFIG_WEBM_IO 19#include "test/webm_video_source.h" 20#endif 21#include "vp9/common/vp9_thread.h" 22 23namespace { 24 25using std::string; 26 27class VP9WorkerThreadTest : public ::testing::TestWithParam<bool> { 28 protected: 29 virtual ~VP9WorkerThreadTest() {} 30 virtual void SetUp() { 31 vp9_get_worker_interface()->init(&worker_); 32 } 33 34 virtual void TearDown() { 35 vp9_get_worker_interface()->end(&worker_); 36 } 37 38 void Run(VP9Worker* worker) { 39 const bool synchronous = GetParam(); 40 if (synchronous) { 41 vp9_get_worker_interface()->execute(worker); 42 } else { 43 vp9_get_worker_interface()->launch(worker); 44 } 45 } 46 47 VP9Worker worker_; 48}; 49 50int ThreadHook(void* data, void* return_value) { 51 int* const hook_data = reinterpret_cast<int*>(data); 52 *hook_data = 5; 53 return *reinterpret_cast<int*>(return_value); 54} 55 56TEST_P(VP9WorkerThreadTest, HookSuccess) { 57 // should be a no-op. 58 EXPECT_NE(vp9_get_worker_interface()->sync(&worker_), 0); 59 60 for (int i = 0; i < 2; ++i) { 61 EXPECT_NE(vp9_get_worker_interface()->reset(&worker_), 0); 62 63 int hook_data = 0; 64 int return_value = 1; // return successfully from the hook 65 worker_.hook = ThreadHook; 66 worker_.data1 = &hook_data; 67 worker_.data2 = &return_value; 68 69 Run(&worker_); 70 EXPECT_NE(vp9_get_worker_interface()->sync(&worker_), 0); 71 EXPECT_FALSE(worker_.had_error); 72 EXPECT_EQ(5, hook_data); 73 74 // should be a no-op. 75 EXPECT_NE(vp9_get_worker_interface()->sync(&worker_), 0); 76 } 77} 78 79TEST_P(VP9WorkerThreadTest, HookFailure) { 80 EXPECT_NE(vp9_get_worker_interface()->reset(&worker_), 0); 81 82 int hook_data = 0; 83 int return_value = 0; // return failure from the hook 84 worker_.hook = ThreadHook; 85 worker_.data1 = &hook_data; 86 worker_.data2 = &return_value; 87 88 Run(&worker_); 89 EXPECT_FALSE(vp9_get_worker_interface()->sync(&worker_)); 90 EXPECT_EQ(1, worker_.had_error); 91 92 // Ensure _reset() clears the error and _launch() can be called again. 93 return_value = 1; 94 EXPECT_NE(vp9_get_worker_interface()->reset(&worker_), 0); 95 EXPECT_FALSE(worker_.had_error); 96 vp9_get_worker_interface()->launch(&worker_); 97 EXPECT_NE(vp9_get_worker_interface()->sync(&worker_), 0); 98 EXPECT_FALSE(worker_.had_error); 99} 100 101TEST_P(VP9WorkerThreadTest, EndWithoutSync) { 102 // Create a large number of threads to increase the chances of detecting a 103 // race. Doing more work in the hook is no guarantee as any race would occur 104 // post hook execution in the main thread loop driver. 105 static const int kNumWorkers = 64; 106 VP9Worker workers[kNumWorkers]; 107 int hook_data[kNumWorkers]; 108 int return_value[kNumWorkers]; 109 110 for (int n = 0; n < kNumWorkers; ++n) { 111 vp9_get_worker_interface()->init(&workers[n]); 112 return_value[n] = 1; // return successfully from the hook 113 workers[n].hook = ThreadHook; 114 workers[n].data1 = &hook_data[n]; 115 workers[n].data2 = &return_value[n]; 116 } 117 118 for (int i = 0; i < 2; ++i) { 119 for (int n = 0; n < kNumWorkers; ++n) { 120 EXPECT_NE(vp9_get_worker_interface()->reset(&workers[n]), 0); 121 hook_data[n] = 0; 122 } 123 124 for (int n = 0; n < kNumWorkers; ++n) { 125 Run(&workers[n]); 126 } 127 128 for (int n = kNumWorkers - 1; n >= 0; --n) { 129 vp9_get_worker_interface()->end(&workers[n]); 130 } 131 } 132} 133 134TEST(VP9WorkerThreadTest, TestInterfaceAPI) { 135 EXPECT_EQ(0, vp9_set_worker_interface(NULL)); 136 EXPECT_TRUE(vp9_get_worker_interface() != NULL); 137 for (int i = 0; i < 6; ++i) { 138 VP9WorkerInterface winterface = *vp9_get_worker_interface(); 139 switch (i) { 140 default: 141 case 0: winterface.init = NULL; break; 142 case 1: winterface.reset = NULL; break; 143 case 2: winterface.sync = NULL; break; 144 case 3: winterface.launch = NULL; break; 145 case 4: winterface.execute = NULL; break; 146 case 5: winterface.end = NULL; break; 147 } 148 EXPECT_EQ(0, vp9_set_worker_interface(&winterface)); 149 } 150} 151 152// ----------------------------------------------------------------------------- 153// Multi-threaded decode tests 154 155#if CONFIG_WEBM_IO 156struct FileList { 157 const char *name; 158 const char *expected_md5; 159}; 160 161// Decodes |filename| with |num_threads|. Returns the md5 of the decoded frames. 162string DecodeFile(const string& filename, int num_threads) { 163 libvpx_test::WebMVideoSource video(filename); 164 video.Init(); 165 166 vpx_codec_dec_cfg_t cfg = vpx_codec_dec_cfg_t(); 167 cfg.threads = num_threads; 168 libvpx_test::VP9Decoder decoder(cfg, 0); 169 170 libvpx_test::MD5 md5; 171 for (video.Begin(); video.cxdata(); video.Next()) { 172 const vpx_codec_err_t res = 173 decoder.DecodeFrame(video.cxdata(), video.frame_size()); 174 if (res != VPX_CODEC_OK) { 175 EXPECT_EQ(VPX_CODEC_OK, res) << decoder.DecodeError(); 176 break; 177 } 178 179 libvpx_test::DxDataIterator dec_iter = decoder.GetDxData(); 180 const vpx_image_t *img = NULL; 181 182 // Get decompressed data 183 while ((img = dec_iter.Next())) { 184 md5.Add(img); 185 } 186 } 187 return string(md5.Get()); 188} 189 190void DecodeFiles(const FileList files[]) { 191 for (const FileList *iter = files; iter->name != NULL; ++iter) { 192 SCOPED_TRACE(iter->name); 193 for (int t = 2; t <= 8; ++t) { 194 EXPECT_EQ(iter->expected_md5, DecodeFile(iter->name, t)) 195 << "threads = " << t; 196 } 197 } 198} 199 200// Trivial serialized thread worker interface implementation. 201// Note any worker that requires synchronization between other workers will 202// hang. 203namespace impl { 204 205void Init(VP9Worker *const worker) { memset(worker, 0, sizeof(*worker)); } 206int Reset(VP9Worker *const /*worker*/) { return 1; } 207int Sync(VP9Worker *const worker) { return !worker->had_error; } 208 209void Execute(VP9Worker *const worker) { 210 worker->had_error |= worker->hook(worker->data1, worker->data2); 211} 212 213void Launch(VP9Worker *const worker) { Execute(worker); } 214void End(VP9Worker *const /*worker*/) {} 215 216} // namespace impl 217 218TEST(VP9WorkerThreadTest, TestSerialInterface) { 219 static const VP9WorkerInterface serial_interface = { 220 impl::Init, impl::Reset, impl::Sync, impl::Launch, impl::Execute, impl::End 221 }; 222 // TODO(jzern): Avoid using a file that will use the row-based thread 223 // loopfilter, with the simple serialized implementation it will hang. This is 224 // due to its expectation that rows will be run in parallel as they wait on 225 // progress in the row above before proceeding. 226 static const char expected_md5[] = "b35a1b707b28e82be025d960aba039bc"; 227 static const char filename[] = "vp90-2-03-size-226x226.webm"; 228 VP9WorkerInterface default_interface = *vp9_get_worker_interface(); 229 230 EXPECT_NE(vp9_set_worker_interface(&serial_interface), 0); 231 EXPECT_EQ(expected_md5, DecodeFile(filename, 2)); 232 233 // Reset the interface. 234 EXPECT_NE(vp9_set_worker_interface(&default_interface), 0); 235 EXPECT_EQ(expected_md5, DecodeFile(filename, 2)); 236} 237 238TEST(VP9DecodeMultiThreadedTest, Decode) { 239 // no tiles or frame parallel; this exercises loop filter threading. 240 EXPECT_EQ("b35a1b707b28e82be025d960aba039bc", 241 DecodeFile("vp90-2-03-size-226x226.webm", 2)); 242} 243 244TEST(VP9DecodeMultiThreadedTest, Decode2) { 245 static const FileList files[] = { 246 { "vp90-2-08-tile_1x2_frame_parallel.webm", 247 "68ede6abd66bae0a2edf2eb9232241b6" }, 248 { "vp90-2-08-tile_1x4_frame_parallel.webm", 249 "368ebc6ebf3a5e478d85b2c3149b2848" }, 250 { "vp90-2-08-tile_1x8_frame_parallel.webm", 251 "17e439da2388aff3a0f69cb22579c6c1" }, 252 { NULL, NULL } 253 }; 254 255 DecodeFiles(files); 256} 257 258// Test tile quantity changes within one file. 259TEST(VP9DecodeMultiThreadedTest, Decode3) { 260 static const FileList files[] = { 261 { "vp90-2-14-resize-fp-tiles-1-16.webm", 262 "0cd5e632c326297e975f38949c31ea94" }, 263 { "vp90-2-14-resize-fp-tiles-1-2-4-8-16.webm", 264 "5c78a96a42e7f4a4f6b2edcdb791e44c" }, 265 { "vp90-2-14-resize-fp-tiles-1-2.webm", 266 "e030450ae85c3277be2a418769df98e2" }, 267 { "vp90-2-14-resize-fp-tiles-1-4.webm", 268 "312eed4e2b64eb7a4e7f18916606a430" }, 269 { "vp90-2-14-resize-fp-tiles-16-1.webm", 270 "1755c16d8af16a9cb3fe7338d90abe52" }, 271 { "vp90-2-14-resize-fp-tiles-16-2.webm", 272 "500300592d3fcb6f12fab25e48aaf4df" }, 273 { "vp90-2-14-resize-fp-tiles-16-4.webm", 274 "47c48379fa6331215d91c67648e1af6e" }, 275 { "vp90-2-14-resize-fp-tiles-16-8-4-2-1.webm", 276 "eecf17290739bc708506fa4827665989" }, 277 { "vp90-2-14-resize-fp-tiles-16-8.webm", 278 "29b6bb54e4c26b5ca85d5de5fed94e76" }, 279 { "vp90-2-14-resize-fp-tiles-1-8.webm", 280 "1b6f175e08cd82cf84bb800ac6d1caa3" }, 281 { "vp90-2-14-resize-fp-tiles-2-16.webm", 282 "ca3b03e4197995d8d5444ede7a6c0804" }, 283 { "vp90-2-14-resize-fp-tiles-2-1.webm", 284 "99aec065369d70bbb78ccdff65afed3f" }, 285 { "vp90-2-14-resize-fp-tiles-2-4.webm", 286 "22d0ebdb49b87d2920a85aea32e1afd5" }, 287 { "vp90-2-14-resize-fp-tiles-2-8.webm", 288 "c2115cf051c62e0f7db1d4a783831541" }, 289 { "vp90-2-14-resize-fp-tiles-4-16.webm", 290 "c690d7e1719b31367564cac0af0939cb" }, 291 { "vp90-2-14-resize-fp-tiles-4-1.webm", 292 "a926020b2cc3e15ad4cc271853a0ff26" }, 293 { "vp90-2-14-resize-fp-tiles-4-2.webm", 294 "42699063d9e581f1993d0cf890c2be78" }, 295 { "vp90-2-14-resize-fp-tiles-4-8.webm", 296 "7f76d96036382f45121e3d5aa6f8ec52" }, 297 { "vp90-2-14-resize-fp-tiles-8-16.webm", 298 "76a43fcdd7e658542913ea43216ec55d" }, 299 { "vp90-2-14-resize-fp-tiles-8-1.webm", 300 "8e3fbe89486ca60a59299dea9da91378" }, 301 { "vp90-2-14-resize-fp-tiles-8-2.webm", 302 "ae96f21f21b6370cc0125621b441fc52" }, 303 { "vp90-2-14-resize-fp-tiles-8-4.webm", 304 "3eb4f24f10640d42218f7fd7b9fd30d4" }, 305 { NULL, NULL } 306 }; 307 308 DecodeFiles(files); 309} 310#endif // CONFIG_WEBM_IO 311 312INSTANTIATE_TEST_CASE_P(Synchronous, VP9WorkerThreadTest, ::testing::Bool()); 313 314} // namespace 315