/* * Copyright © 2014 The TOVA Company * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ /** * \file timing.c * Validates that OML Sync Control implementation actually syncs to vertical * retrace */ #include "piglit-util-gl.h" #include "piglit-glx-util.h" #include "common.h" /* * TODO: varying MSC deltas enumerated as arguments * TODO: As a different test, create two drawables and verify they have * independent SBC */ static bool fullscreen; static bool use_swapbuffers = true; static int64_t target_msc_delta; static int64_t divisor; static const int64_t msc_remainder = 0; static const unsigned int loops = 10; struct stats { unsigned int n; double mean; double M2; }; static void update_stats(struct stats *stats, double val) { double delta = val - stats->mean; stats->n += 1; stats->mean += delta / stats->n; stats->M2 += delta * (val - stats->mean); } static double get_stddev(struct stats *stats) { return sqrt(stats->M2 / (stats->n - 1)); } static bool swap_buffers_msc(Display *dpy, int64_t last_sbc, int64_t *target_sbc, int64_t target_msc, int64_t divisor, int64_t remainder, int64_t *new_ust, int64_t *new_msc, int64_t *new_sbc, enum piglit_result *result) { glClearColor(0.0, 1.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); *target_sbc = glXSwapBuffersMscOML(dpy, win, target_msc, divisor, remainder); if (*target_sbc <= 0) { fprintf(stderr, "SwapBuffersMscOML failed\n"); return false; } if (*target_sbc != last_sbc + 1) { fprintf(stderr, "glXSwapBuffersMscOML calculated the wrong target sbc: " "expected %"PRId64" but got %"PRId64"\n", last_sbc + 1, *target_sbc); *result = PIGLIT_FAIL; } if (!glXWaitForSbcOML(dpy, win, *target_sbc, new_ust, new_msc, new_sbc)) { fprintf(stderr, "glXWaitForSbcOML failed\n"); *result = PIGLIT_FAIL; } return true; } static void wait_for_msc(Display *dpy, int64_t target_msc, int64_t divisor, int64_t remainder, int64_t *new_ust, int64_t *new_msc, int64_t *new_sbc, enum piglit_result *result) { if (!glXWaitForMscOML(dpy, win, target_msc, divisor, remainder, new_ust, new_msc, new_sbc)) { fprintf(stderr, "glXWaitForMscOML failed\n"); *result = PIGLIT_FAIL; } } static enum piglit_result draw(Display *dpy) { enum piglit_result result = PIGLIT_PASS; int64_t last_ust = 0xd0, last_msc = 0xd0, last_sbc = 0xd0; int64_t last_timestamp = -1; /* in nano seconds */ struct stats msc_wallclock_duration_stats = {}; struct stats msc_ust_duration_stats = {}; double expected_msc_wallclock_duration = 0.0; int32_t rate_num, rate_den; unsigned int i; if (!glXGetSyncValuesOML(dpy, win, &last_ust, &last_msc, &last_sbc)) { fprintf(stderr, "Initial glXGetSyncValuesOML failed\n"); return PIGLIT_FAIL; } /* Check that the window is fresh */ if (last_sbc != 0) { fprintf(stderr, "Initial SBC for the window should be 0, was " "%" PRId64 "\n", last_sbc); piglit_merge_result(&result, PIGLIT_WARN); } if (!glXGetMscRateOML(dpy, win, &rate_num, &rate_den)) { fprintf(stderr, "glXGetMscRateOML failed, can't test MSC duration\n"); piglit_merge_result(&result, PIGLIT_WARN); } else { expected_msc_wallclock_duration = 1e6 * rate_den / rate_num; } piglit_set_timeout(5, PIGLIT_FAIL); /* Perform a swap / wait once before the loop. Without this, the first * loop iteration can start at any point of the display refresh cycle. * If we called glXGetSyncValuesOML shortly before a vertical blank * period, it may be too late for the first iteration swap / wait to * hit the target MSC, which would result in at least a warning. */ if (use_swapbuffers) { if (!swap_buffers_msc(dpy, last_sbc, &last_sbc, last_msc + 1, 0, 0, &last_ust, &last_msc, &last_sbc, &result)) return PIGLIT_FAIL; } else { wait_for_msc(dpy, last_msc + 1, 0, 0, &last_ust, &last_msc, &last_sbc, &result); } for (i = 0; i < loops; i++) { int64_t new_ust = 0xd0, new_msc = 0xd0, new_sbc = 0xd0; int64_t check_ust = 0xd0, check_msc = 0xd0, check_sbc = 0xd0; int64_t new_timestamp; /* in nano seconds */ int64_t expected_msc, target_sbc; int64_t target_msc = 0; if (target_msc_delta) { target_msc = last_msc + target_msc_delta; } if (use_swapbuffers) { if (!swap_buffers_msc(dpy, last_sbc, &target_sbc, target_msc, divisor, msc_remainder, &new_ust, &new_msc, &new_sbc, &result)) return PIGLIT_FAIL; } else { target_sbc = last_sbc; wait_for_msc(dpy, target_msc, divisor, msc_remainder, &new_ust, &new_msc, &new_sbc, &result); } new_timestamp = piglit_time_get_nano(); if (!glXGetSyncValuesOML(dpy, win, &check_ust, &check_msc, &check_sbc)) { fprintf(stderr, "Follow-up GetSyncValuesOML failed\n"); return PIGLIT_FAIL; } if (new_ust < last_ust) { fprintf(stderr, "iteration %u: non-monotonic UST went " "backward by %"PRId64" during Wait\n", i, last_ust - new_ust); result = PIGLIT_FAIL; /* Wait returned something bogus, but GetSyncValues * usually works, so try evaluating the rest of the * tests using the check values. */ new_ust = check_ust; } if (check_ust < new_ust) { fprintf(stderr, "iteration %u: non-monotonic UST went " "backward by %"PRId64" across GetSyncValues\n", i, last_ust - check_ust); result = PIGLIT_FAIL; } if (new_msc < last_msc) { fprintf(stderr, "iteration %u: non-monotonic MSC went " "backward by %"PRId64" during Wait\n", i, last_msc - new_msc); result = PIGLIT_FAIL; /* Wait returned something bogus, but GetSyncValues * usually works, so try evaluating the rest of the * tests using the check values. */ new_msc = check_msc; } if (check_msc < new_msc) { fprintf(stderr, "iteration %u: non-monotonic MSC went " "backward by %"PRId64" across GetSyncValues\n", i, last_msc - check_msc); result = PIGLIT_FAIL; } if (new_sbc != target_sbc) { fprintf(stderr, "iteration %u: Wait should have " "returned at SBC %"PRId64" but returned at " "%"PRId64"\n", i, target_sbc, new_sbc); result = PIGLIT_FAIL; } if (check_sbc != new_sbc) { fprintf(stderr, "iteration %u: GetSyncValues " "returned SBC %"PRId64" but Wait returned " "%"PRId64"\n", i, check_sbc, new_sbc); result = PIGLIT_FAIL; } if (new_msc > last_msc) { int64_t delta_msc = new_msc - last_msc; update_stats(&msc_ust_duration_stats, (new_ust - last_ust) / delta_msc); if (last_timestamp >= 0) { if (!piglit_time_is_monotonic()) { fprintf(stderr, "no monotonic clock\n"); piglit_merge_result(&result, PIGLIT_WARN); } else { update_stats( &msc_wallclock_duration_stats, (new_timestamp - last_timestamp) / (delta_msc*1000)); } } } expected_msc = target_msc; if (!target_msc) { /* If there is a divisor, the expected MSC is the * next MSC after last_msc such that * MSC % divisor == remainder */ int64_t last_remainder = last_msc % divisor; expected_msc = last_msc - last_remainder + msc_remainder; if (expected_msc <= last_msc) expected_msc += divisor; } if (new_msc < expected_msc) { fprintf(stderr, "iteration %u woke up %"PRId64 " MSCs early\n", i, expected_msc - new_msc); result = PIGLIT_FAIL; } if (new_msc > expected_msc) { fprintf(stderr, "iteration %u woke up %"PRId64 " MSCs later than expected\n", i, new_msc - expected_msc); piglit_merge_result(&result, PIGLIT_WARN); } if (new_msc % divisor != msc_remainder) { fprintf(stderr, "iteration %u woke up at wrong MSC" " remainder %"PRId64", not requested remainder" " %"PRId64"\n", i, new_msc % divisor, msc_remainder); result = PIGLIT_FAIL; } last_ust = new_ust; last_msc = new_msc; last_sbc = new_sbc; last_timestamp = new_timestamp; } if (msc_ust_duration_stats.n < 2) { fprintf(stderr, "Not enough UST timing samples\n"); piglit_merge_result(&result, PIGLIT_WARN); } else if (expected_msc_wallclock_duration > 0.0) { double apparent_ust_rate = msc_ust_duration_stats.mean / expected_msc_wallclock_duration; if (get_stddev(&msc_ust_duration_stats) / apparent_ust_rate > 100) { fprintf(stderr, "UST duration per MSC is surprisingly" " variable (stddev %f USTs), but then it only" " has to be monotonic\n", get_stddev(&msc_ust_duration_stats)); piglit_merge_result(&result, PIGLIT_WARN); } } if (msc_wallclock_duration_stats.n < 2) { fprintf(stderr, "Not enough wallclock timing samples\n"); piglit_merge_result(&result, PIGLIT_WARN); } else if (get_stddev(&msc_wallclock_duration_stats) > 1000) { fprintf(stderr, "Wallclock time between MSCs has stddev > 1ms" " (%fus), driver is probably not syncing to" " vblank\n", get_stddev(&msc_wallclock_duration_stats)); result = PIGLIT_FAIL; } else if (expected_msc_wallclock_duration > 0.0) { if (fabs(expected_msc_wallclock_duration - msc_wallclock_duration_stats.mean) > 50) { fprintf(stderr, "Wallclock time between MSCs %fus" " does not match glXGetMscRateOML %fus\n", msc_wallclock_duration_stats.mean, expected_msc_wallclock_duration); result = PIGLIT_FAIL; } } return result; } static unsigned int parse_num_arg(int argc, char **argv, int j) { char *ptr; unsigned int val; if (j >= argc) { fprintf(stderr, "%s requires an argument\n", argv[j - 1]); piglit_report_result(PIGLIT_FAIL); } val = strtoul(argv[j], &ptr, 0); if (!val || *ptr != '\0') { fprintf(stderr, "%s requires an argument\n", argv[j - 1]); piglit_report_result(PIGLIT_FAIL); } return val; } int main(int argc, char **argv) { int j; for (j = 1; j < argc; j++) { if (!strcmp(argv[j], "-fullscreen")) { fullscreen = true; } else if (!strcmp(argv[j], "-waitformsc")) { use_swapbuffers = false; } else if (!strcmp(argv[j], "-divisor")) { j++; divisor = parse_num_arg(argc, argv, j); } else if (!strcmp(argv[j], "-msc-delta")) { j++; target_msc_delta = parse_num_arg(argc, argv, j); } else if (!strcmp(argv[j], "-auto")) { piglit_automatic = true; } else { fprintf(stderr, "unsupported option %s\n", argv[j]); piglit_report_result(PIGLIT_FAIL); } } if (divisor && target_msc_delta) { fprintf(stderr, "this test doesn't support using both " "-divisor and -msc-delta\n"); piglit_report_result(PIGLIT_FAIL); } if (!use_swapbuffers && !divisor && !target_msc_delta) { fprintf(stderr, "when using -waitformsc, this test requires " "either -divisor or -msc-delta\n"); piglit_report_result(PIGLIT_FAIL); } if (!divisor) { divisor=1; } piglit_automatic = true; piglit_oml_sync_control_test_run(fullscreen, draw); return 0; }