| /* |
| * Copyright (c) 2026, Alliance for Open Media. All rights reserved. |
| * |
| * This source code is subject to the terms of the BSD 2 Clause License and |
| * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License |
| * was not distributed with this source code in the LICENSE file, you can |
| * obtain it at www.aomedia.org/license/software. If the Alliance for Open |
| * Media Patent License 1.0 was not distributed with this source code in the |
| * PATENTS file, you can obtain it at www.aomedia.org/license/patent. |
| */ |
| |
| // Test that av1_add_film_grain is thread-safe when called concurrently |
| // with images of different chroma subsampling (4:2:0 vs 4:4:4). |
| // Reproduces the data race on static globals in grain_synthesis.c. |
| |
| #include <cstring> |
| #include <vector> |
| |
| #include "gtest/gtest.h" |
| #include "aom/aom_image.h" |
| #include "aom_dsp/grain_params.h" |
| #include "aom_util/aom_pthread.h" |
| #include "av1/decoder/grain_synthesis.h" |
| |
| namespace { |
| |
| // Minimal film grain params that trigger the overlap path. |
| aom_film_grain_t MakeGrainParams() { |
| aom_film_grain_t params = {}; |
| params.apply_grain = 1; |
| params.update_parameters = 1; |
| params.num_y_points = 2; |
| params.scaling_points_y[0][0] = 0; |
| params.scaling_points_y[0][1] = 96; |
| params.scaling_points_y[1][0] = 255; |
| params.scaling_points_y[1][1] = 96; |
| params.num_cb_points = 2; |
| params.scaling_points_cb[0][0] = 0; |
| params.scaling_points_cb[0][1] = 64; |
| params.scaling_points_cb[1][0] = 255; |
| params.scaling_points_cb[1][1] = 64; |
| params.num_cr_points = 2; |
| params.scaling_points_cr[0][0] = 0; |
| params.scaling_points_cr[0][1] = 64; |
| params.scaling_points_cr[1][0] = 255; |
| params.scaling_points_cr[1][1] = 64; |
| params.scaling_shift = 11; |
| params.ar_coeff_lag = 1; |
| params.ar_coeff_shift = 7; |
| params.overlap_flag = 1; |
| params.bit_depth = 8; |
| params.random_seed = 7391; |
| params.cb_mult = 128; |
| params.cb_luma_mult = 192; |
| params.cb_offset = 256; |
| params.cr_mult = 128; |
| params.cr_luma_mult = 192; |
| params.cr_offset = 256; |
| return params; |
| } |
| |
| void RunGrain(aom_img_fmt_t fmt, int iterations) { |
| constexpr int kWidth = 128; |
| constexpr int kHeight = 128; |
| |
| aom_image_t src; |
| ASSERT_NE(aom_img_alloc(&src, fmt, kWidth, kHeight, 32), nullptr); |
| memset(src.planes[AOM_PLANE_Y], 128, |
| (size_t)src.stride[AOM_PLANE_Y] * kHeight); |
| const int chroma_h = (fmt == AOM_IMG_FMT_I420) ? kHeight / 2 : kHeight; |
| memset(src.planes[AOM_PLANE_U], 128, |
| (size_t)src.stride[AOM_PLANE_U] * chroma_h); |
| memset(src.planes[AOM_PLANE_V], 128, |
| (size_t)src.stride[AOM_PLANE_V] * chroma_h); |
| src.bit_depth = 8; |
| src.mc = AOM_CICP_MC_BT_709; |
| |
| aom_image_t dst; |
| ASSERT_NE(aom_img_alloc(&dst, fmt, kWidth, kHeight, 32), nullptr); |
| dst.bit_depth = 8; |
| dst.mc = AOM_CICP_MC_BT_709; |
| |
| aom_film_grain_t params = MakeGrainParams(); |
| |
| for (int i = 0; i < iterations; ++i) { |
| params.random_seed = (uint16_t)(7391 + i); |
| int ret = av1_add_film_grain(¶ms, &src, &dst); |
| ASSERT_EQ(ret, 0) << "av1_add_film_grain failed on iteration " << i; |
| } |
| |
| aom_img_free(&src); |
| aom_img_free(&dst); |
| } |
| |
| constexpr int kIterations = 200; |
| |
| THREADFN RunGrain420(void * /*arg*/) { |
| RunGrain(AOM_IMG_FMT_I420, kIterations); |
| return THREAD_EXIT_SUCCESS; |
| } |
| |
| THREADFN RunGrain444(void * /*arg*/) { |
| RunGrain(AOM_IMG_FMT_I444, kIterations); |
| return THREAD_EXIT_SUCCESS; |
| } |
| |
| TEST(GrainSynthesisRaceTest, ConcurrentDifferentSubsampling) { |
| constexpr int kThreads = 4; |
| |
| std::vector<pthread_t> threads; |
| threads.reserve(kThreads * 2); |
| |
| for (int i = 0; i < kThreads; ++i) { |
| pthread_t thread; |
| ASSERT_EQ(pthread_create(&thread, nullptr, RunGrain420, nullptr), 0); |
| threads.push_back(thread); |
| ASSERT_EQ(pthread_create(&thread, nullptr, RunGrain444, nullptr), 0); |
| threads.push_back(thread); |
| } |
| |
| for (auto thread : threads) { |
| ASSERT_EQ(pthread_join(thread, nullptr), 0); |
| } |
| } |
| |
| } // namespace |