Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
806 changes: 472 additions & 334 deletions SerialPrograms/Source/Kernels/BinaryMatrix/Kernels_BinaryMatrix_Tests.cpp

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -300,13 +300,20 @@ PackedBinaryMatrixCore<Tile> PackedBinaryMatrixCore<Tile>::submatrix(
}

#if 1
// Clear the bits past the right and bottom edges, which came from outside the
// box. Skip an edge that ends at a tile boundary: `clear_padding()` with 0 bits
// would clear the whole last column or row of tiles.
size_t wbits = width % TILE_WIDTH;
for (size_t r = 0; r < tile_height; r++){
ret.tile(tile_width - 1, r).clear_padding(wbits, TILE_HEIGHT);
if (wbits != 0){
for (size_t r = 0; r < tile_height; r++){
ret.tile(tile_width - 1, r).clear_padding(wbits, TILE_HEIGHT);
}
}
size_t hbits = height % TILE_HEIGHT;
for (size_t c = 0; c < tile_width; c++){
ret.tile(c, tile_height - 1).clear_padding(TILE_WIDTH, hbits);
if (hbits != 0){
for (size_t c = 0; c < tile_width; c++){
ret.tile(c, tile_height - 1).clear_padding(TILE_WIDTH, hbits);
}
}
#endif

Expand Down
919 changes: 410 additions & 509 deletions SerialPrograms/Source/Kernels/ImageFilters/Kernels_ImageFilter_Tests.cpp

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ void filter_rgb32_range(
image, bytes_per_row, width, height,
filter[c].data, filter[c].bytes_per_row,
filter[c].replacement,
filter[c].pixels_in_range,
filter[c].invert,
filter[c].mins,
filter[c].maxs
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ struct FilterRgb32RangeFilter{
, invert(p_invert)
, mins(p_mins)
, maxs(p_maxs)
, pixels_in_range(0)
{}
};
void filter_rgb32_range(
Expand Down Expand Up @@ -94,6 +95,7 @@ struct ToBlackWhiteRgb32RangeFilter{
, mins(p_mins)
, maxs(p_maxs)
, in_range_black(p_in_range_black)
, pixels_in_range(0)
{}
};
void to_blackwhite_rgb32_range(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,55 +4,157 @@
*
*/

//#include "Common/Cpp/Color.h"
#include "Common/Cpp/Time.h"
#include <algorithm>
#include <format>
#include "Common/Cpp/CpuId/CpuId.h"
#include "Common/Cpp/TestRunners/UnitTestDatabase.h"
#include "CommonFramework/GlobalAutoPaths.h"
#include "CommonFramework/ImageTypes/ImageRGB32.h"
#include "CommonFramework/ImageTypes/ImageViewRGB32.h"
//#include "Kernels/BinaryMatrix/Kernels_BinaryMatrix_Arch_64xH_Default.h"
#include "Kernels/ImageScaleBrightness/Kernels_ImageScaleBrightness.h"
#include "Tests/GuardPageImage.h"
#include "Kernels_ImageScaleBrightness_Tests.h"

#include <iostream>
using std::cout;
using std::endl;

namespace PokemonAutomation{
namespace Kernels{


// The scale_brightness() implementations, defined in Kernels_ImageScaleBrightness_*.cpp.
// `scale_brightness()` in Kernels_ImageScaleBrightness.cpp declares the same functions
// and calls the best one for the CPU. These tests call them directly, so that every
// implementation this CPU supports is tested.
void scale_brightness_Default (size_t width, size_t height, uint32_t* image, size_t bytes_per_row, float scaleR, float scaleG, float scaleB);
void scale_brightness_x64_SSE41 (size_t width, size_t height, uint32_t* image, size_t bytes_per_row, float scaleR, float scaleG, float scaleB);
void scale_brightness_x64_AVX2 (size_t width, size_t height, uint32_t* image, size_t bytes_per_row, float scaleR, float scaleG, float scaleB);
void scale_brightness_x64_AVX512 (size_t width, size_t height, uint32_t* image, size_t bytes_per_row, float scaleR, float scaleG, float scaleB);
void scale_brightness_arm64_NEON (size_t width, size_t height, uint32_t* image, size_t bytes_per_row, float scaleR, float scaleG, float scaleB);


namespace{

// One scale_brightness() implementation to test: its name in the test name, and the function.
struct ScaleBrightnessImplementation{
using Function = void (*)(
size_t width, size_t height, uint32_t* image, size_t bytes_per_row,
float scaleR, float scaleG, float scaleB
);

std::string name;
Function scale_brightness;
};

// The (R, G, B) scales to test: below 1, above 1 so that some channels reach the 255
// cap, exactly 1, and a negative scale, which turns the channel to 0.
constexpr float SCALES[][3] = {
{1.2f, 1.3f, 0.5f},
{0.7f, 2.5f, 1.0f},
{1.0f, -1.0f, 3.0f},
};

// The expected result for one pixel. Each of R, G and B is multiplied by its scale
// (a negative scale counts as 0), truncated to an integer and capped at 255. Alpha
// does not change. For example, with scale 1.3 a channel of 102 becomes 132 (132.6
// truncated), and a channel of 200 becomes 255.
uint32_t scaled_pixel(uint32_t pixel, float scaleR, float scaleG, float scaleB){
auto scale_channel = [](uint32_t value, float scale){
float scaled = (float)value * std::max(scale, 0.0f);
return std::min((uint32_t)scaled, (uint32_t)255);
};
return (pixel & 0xff000000)
| scale_channel((pixel >> 16) & 0xff, scaleR) << 16
| scale_channel((pixel >> 8) & 0xff, scaleG) << 8
| scale_channel(pixel & 0xff, scaleB);
}


// Check one scale_brightness() implementation on a real image, for each of `SCALES`:
// scale a copy of the image in place and compare every pixel with `scaled_pixel()`.
// The copy is a guard-page image, so the test also crashes if the implementation
// reads or writes past the last pixel.
class Test_ImageScaleBrightness : public UnitTest{
public:
Test_ImageScaleBrightness(
const ScaleBrightnessImplementation& implementation,
const std::string& image
)
: UnitTest("Kernels::ImageScaleBrightness - " + image)
: UnitTest("Kernels::ImageScaleBrightness - " + implementation.name + " - " + image)
, m_implementation(implementation)
, m_image(UNIT_TEST_RESOURCE_PATH() + image)
{}

virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{
ImageRGB32 new_image(m_image);
ImageRGB32 image(m_image);

int num_iterations = 500;
auto time_start = current_time();
for (int i = 0; i < num_iterations; i++){
scale_brightness(new_image.width(), new_image.height(), new_image.data(), new_image.bytes_per_row(), 1.2f, 1.3f, 0.5f);
// break;
for (const auto& scale : SCALES){
GuardPageImageRGB32 scaled(image);
m_implementation.scale_brightness(
scaled.width(), scaled.height(), scaled.data(), scaled.bytes_per_row(),
scale[0], scale[1], scale[2]
);
for (size_t y = 0; y < image.height(); y++){
for (size_t x = 0; x < image.width(); x++){
const uint32_t pixel = image.pixel(x, y);
const uint32_t expected = scaled_pixel(pixel, scale[0], scale[1], scale[2]);
if (scaled.pixel(x, y) != expected){
return std::format(
"Error: scales ({}, {}, {}), pixel ({}, {}) is {:08x}: got {:08x} but should be {:08x}.",
scale[0], scale[1], scale[2], x, y, pixel, scaled.pixel(x, y), expected
);
}
}
}
}
auto time_end = current_time();
const auto ms = std::chrono::duration_cast<Milliseconds>(time_end - time_start).count();
cout << "Time: " << ms << " ms, " << ms / 1000. << " s" << endl;

// new_image.save("./output.png");

return true;
};
}

private:
ScaleBrightnessImplementation m_implementation;
std::string m_image;
};

}



// Add a test for each image and each scale_brightness() implementation that this build
// has and this CPU supports, including Default.
void add_tests_ImageScaleBrightness(UnitTestDatabase& database){
std::vector<ScaleBrightnessImplementation> implementations{
{"Default", scale_brightness_Default},
};
#ifdef PA_AutoDispatch_x64_08_Nehalem
if (CPU_CAPABILITY_CURRENT.OK_08_Nehalem){
implementations.push_back({"x64_SSE41", scale_brightness_x64_SSE41});
}
#endif
#ifdef PA_AutoDispatch_x64_13_Haswell
if (CPU_CAPABILITY_CURRENT.OK_13_Haswell){
implementations.push_back({"x64_AVX2", scale_brightness_x64_AVX2});
}
#endif
#ifdef PA_AutoDispatch_x64_17_Skylake
if (CPU_CAPABILITY_CURRENT.OK_17_Skylake){
implementations.push_back({"x64_AVX512", scale_brightness_x64_AVX512});
}
#endif
#ifdef PA_AutoDispatch_arm64_20_M1
if (CPU_CAPABILITY_CURRENT.OK_M1){
implementations.push_back({"arm64_NEON", scale_brightness_arm64_NEON});
}
#endif

// Images in the CommandLineTests repo. Their widths cover every remainder mod 4, so
// the leftover pixels after the 2- and 4-pixel loops are tested too.
const std::vector<std::string> images{
"NintendoSwitch/Switch2/ghostassassin/system_settings_display_dark.png", // 1916 x 1078
"OCR/Lv.1-0.png", // 237 x 77
"PokemonSwSh/SelectionArrowFinder/mac_neon_error_1.png", // 106 x 410
"OCR/german-nature-sanft.png", // 511 x 91
};

for (const ScaleBrightnessImplementation& implementation : implementations){
for (const std::string& image : images){
database.add<Test_ImageScaleBrightness>(implementation, image);
}
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ PA_FORCE_INLINE void scale_brightness_x64_AVX2(
pf = _mm256_mul_ps(pf, scale);
pf = _mm256_min_ps(pf, _mm256_set1_ps(255.));
pf = _mm256_max_ps(pf, _mm256_set1_ps(0.));
pi = _mm256_cvtps_epi32(pf);
pi = _mm256_cvttps_epi32(pf);
pi = _mm256_shuffle_epi8(pi, _mm256_setr_epi8(
0, 4, 8, 12, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, 0, 4, 8, 12, -1, -1, -1, -1, -1, -1, -1, -1
Expand All @@ -54,7 +54,7 @@ PA_FORCE_INLINE void scale_brightness_x64_AVX2(
pf = _mm_mul_ps(pf, _mm256_castps256_ps128(scale));
pf = _mm_min_ps(pf, _mm_set1_ps(255.));
pf = _mm_max_ps(pf, _mm_set1_ps(0.));
pi = _mm_cvtps_epi32(pf);
pi = _mm_cvttps_epi32(pf);
pi = _mm_shuffle_epi8(pi, _mm_setr_epi8(0, 4, 8, 12, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1));

image[0] = _mm_cvtsi128_si32(pi);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ PA_FORCE_INLINE void scale_brightness_x64_AVX512(
pf = _mm512_mul_ps(pf, scale);
pf = _mm512_min_ps(pf, _mm512_set1_ps(255.));
pf = _mm512_max_ps(pf, _mm512_set1_ps(0.));
pi = _mm512_cvtps_epi32(pf);
pi = _mm512_cvttps_epi32(pf);
pixel = _mm512_cvtepi32_epi8(pi);

_mm_storeu_si128((__m128i*)image, pixel);
Expand All @@ -48,7 +48,7 @@ PA_FORCE_INLINE void scale_brightness_x64_AVX512(
pf = _mm512_mul_ps(pf, scale);
pf = _mm512_min_ps(pf, _mm512_set1_ps(255.));
pf = _mm512_max_ps(pf, _mm512_set1_ps(0.));
pi = _mm512_cvtps_epi32(pf);
pi = _mm512_cvttps_epi32(pf);
pixel = _mm512_cvtepi32_epi8(pi);

_mm_mask_storeu_epi32((__m128i*)image, mask, pixel);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ PA_FORCE_INLINE void scale_brightness_x64_SSE41(
pf = _mm_mul_ps(pf, scale);
pf = _mm_min_ps(pf, _mm_set1_ps(255.));
pf = _mm_max_ps(pf, _mm_set1_ps(0.));
pi = _mm_cvtps_epi32(pf);
pi = _mm_cvttps_epi32(pf);
pi = _mm_shuffle_epi8(pi, _mm_setr_epi8(0, 4, 8, 12, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1));

image[c] = _mm_cvtsi128_si32(pi);
Expand Down
Loading