WinterGram/third-party/subcodec/test/test_mux_perf.cpp
2026-04-07 09:58:54 +02:00

214 lines
7.8 KiB
C++

#include "types.h"
#include "mbs_encode.h"
#include "mux_surface.h"
#include "mbs_mux_common.h"
#include <cstdio>
#include <cstring>
#include <chrono>
#include <vector>
#include <cstdlib>
#include <algorithm>
#include <numeric>
using namespace subcodec;
static constexpr int SPRITE_W = 6;
static constexpr int SPRITE_H = 6;
static constexpr int PADDING = 1;
static constexpr int NUM_FRAMES = 160;
static constexpr int NUM_SPRITES = 1764;
static constexpr uint8_t QP = 26;
/* Build a synthetic MbsSprite with realistic content:
* Frame 0: I_16x16 DC border, I_16x16 DC-only content (4x4 inner)
* Frames 1-159: SKIP border, P_16x16 content with small MVs,
* ~50% of content MBs have coded residual */
static MbsSprite make_sprite() {
const int num_mbs = SPRITE_W * SPRITE_H;
FrameParams fp{};
fp.width_mbs = SPRITE_W;
fp.height_mbs = SPRITE_H;
fp.qp = QP;
std::vector<MbsEncodedFrame> frames(NUM_FRAMES);
for (int f = 0; f < NUM_FRAMES; f++) {
std::vector<MacroblockData> mbs(num_mbs);
for (int row = 0; row < SPRITE_H; row++) {
for (int col = 0; col < SPRITE_W; col++) {
int idx = row * SPRITE_W + col;
bool is_border = (row < PADDING || row >= SPRITE_H - PADDING ||
col < PADDING || col >= SPRITE_W - PADDING);
if (f == 0) {
// IDR frame
if (is_border) {
mbs[idx].mb_type = MbType::I_16x16;
mbs[idx].intra_pred_mode = I16PredMode::DC;
mbs[idx].intra_chroma_mode = ChromaPredMode::DC;
} else {
mbs[idx].mb_type = MbType::I_16x16;
mbs[idx].intra_pred_mode = I16PredMode::DC;
mbs[idx].intra_chroma_mode = ChromaPredMode::DC;
// DC-only coefficients
mbs[idx].luma_dc[0] = (int16_t)(50 + row * 10 + col * 5);
mbs[idx].cbp_luma = 0;
mbs[idx].cbp_chroma = 0;
}
} else {
// P-frame
if (is_border) {
mbs[idx].mb_type = MbType::SKIP;
} else {
mbs[idx].mb_type = MbType::P_16x16;
// Small MVs
mbs[idx].mv_x = (int16_t)((col % 3) - 1);
mbs[idx].mv_y = (int16_t)((row % 3) - 1);
// ~50% of content MBs have coded residual
if ((row + col + f) % 2 == 0) {
mbs[idx].cbp_luma = 0x01; // first 8x8 block has coeffs
mbs[idx].cbp_chroma = 1;
// Sparse coefficients
mbs[idx].luma_ac[0][0] = (int16_t)(3 + (f % 5));
mbs[idx].luma_ac[0][1] = (int16_t)(-(f % 3));
mbs[idx].cb_ac[0][0] = 2;
mbs[idx].cr_ac[0][0] = -1;
}
}
}
}
}
// Encode merged color+alpha frame (alpha is all-skip)
std::vector<MacroblockData> alpha_mbs(num_mbs);
for (auto& mb : alpha_mbs) mb.mb_type = MbType::SKIP;
frames[f] = mbs::encode_frame_merged(fp, mbs.data(), fp, alpha_mbs.data(), SPRITE_W, PADDING);
}
MbsSprite sp;
sp.width_mbs = SPRITE_W;
sp.height_mbs = SPRITE_H;
sp.num_frames = NUM_FRAMES;
sp.qp = QP;
sp.qp_delta_idr = 0;
sp.qp_delta_p = 0;
sp.set_frames(std::move(frames));
return sp;
}
int main() {
printf("=== Mux Performance Stress Test ===\n");
printf("Sprites: %d, Frames: %d, Sprite size: %dx%d MBs\n",
NUM_SPRITES, NUM_FRAMES, SPRITE_W, SPRITE_H);
// 1. Generate synthetic sprite
printf("Generating synthetic sprite...\n");
auto t0 = std::chrono::high_resolution_clock::now();
MbsSprite template_sprite = make_sprite();
auto t1 = std::chrono::high_resolution_clock::now();
double gen_ms = std::chrono::duration<double, std::milli>(t1 - t0).count();
printf(" Sprite generation: %.1f ms\n", gen_ms);
// 2. Create MuxSurface
size_t total_bytes = 0;
auto sink = [&total_bytes](std::span<const uint8_t> data) {
total_bytes += data.size();
};
MuxSurface::Params params;
params.sprite_width = (SPRITE_W - 2) * 16;
params.sprite_height = (SPRITE_H - 2) * 16;
params.max_slots = NUM_SPRITES;
params.qp = QP;
params.qp_delta_idr = 0;
params.qp_delta_p = 0;
auto mux_result = MuxSurface::create(params, sink);
if (!mux_result) {
printf("FAIL: MuxSurface::create failed\n");
return 1;
}
auto& mux = *mux_result;
printf("Grid: %dx%d MBs (%dx%d pixels)\n",
mux.width_mbs(), mux.height_mbs(),
mux.width_mbs() * 16, mux.height_mbs() * 16);
// Save template sprite to temp file for reloading copies
const char* tmp_path = "/tmp/test_mux_perf_template.mbs";
auto save_result = template_sprite.save(tmp_path);
if (!save_result) { printf("FAIL: save template\n"); return 1; }
// 3. Add 1764 copies of the sprite
printf("Adding %d sprites...\n", NUM_SPRITES);
auto t_add_start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < NUM_SPRITES; i++) {
auto slot = mux.add_sprite(tmp_path);
if (!slot) {
printf("FAIL: add_sprite failed at slot %d\n", i);
return 1;
}
}
auto t_add_end = std::chrono::high_resolution_clock::now();
double add_ms = std::chrono::duration<double, std::milli>(t_add_end - t_add_start).count();
printf(" Sprite add time: %.1f ms (%.2f ms/sprite)\n", add_ms, add_ms / NUM_SPRITES);
// 4. Advance 160 frames, measuring per-frame time
printf("Advancing %d frames...\n", NUM_FRAMES);
std::vector<double> frame_times(NUM_FRAMES);
for (int f = 0; f < NUM_FRAMES; f++) {
auto fs = std::chrono::high_resolution_clock::now();
auto result = mux.advance_frame(sink);
auto fe = std::chrono::high_resolution_clock::now();
if (!result) {
printf("FAIL: advance_frame failed at frame %d\n", f);
return 1;
}
frame_times[f] = std::chrono::duration<double, std::milli>(fe - fs).count();
if (f < 3 || f == NUM_FRAMES - 1) {
printf(" Frame %3d: %.1f ms\n", f, frame_times[f]);
} else if (f == 3) {
printf(" ...\n");
}
}
// 5. Compute statistics
std::vector<double> sorted_times = frame_times;
std::sort(sorted_times.begin(), sorted_times.end());
double total_time = std::accumulate(frame_times.begin(), frame_times.end(), 0.0);
double avg = total_time / NUM_FRAMES;
double min_t = sorted_times.front();
double max_t = sorted_times.back();
double p50 = sorted_times[NUM_FRAMES / 2];
double p95 = sorted_times[(int)(NUM_FRAMES * 0.95)];
double p99 = sorted_times[(int)(NUM_FRAMES * 0.99)];
printf("\n=== Results ===\n");
printf("Grid size: %dx%d MBs (%dx%d pixels)\n",
mux.width_mbs(), mux.height_mbs(),
mux.width_mbs() * 16, mux.height_mbs() * 16);
printf("Total bytes: %zu (%.1f MB)\n", total_bytes, total_bytes / (1024.0 * 1024.0));
printf("Total time: %.1f ms\n", total_time);
printf("Per-frame avg: %.2f ms\n", avg);
printf("Per-frame min: %.2f ms\n", min_t);
printf("Per-frame p50: %.2f ms\n", p50);
printf("Per-frame p95: %.2f ms\n", p95);
printf("Per-frame p99: %.2f ms\n", p99);
printf("Per-frame max: %.2f ms\n", max_t);
// 6. Sanity check
if (total_bytes == 0) {
printf("\nFAIL: total_bytes == 0\n");
return 1;
}
printf("\nPASS\n");
return 0;
}