Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1313)

Unified Diff: source/libvpx/vp9/encoder/vp9_firstpass.c

Issue 668403002: libvpx: Pull from upstream (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/deps/third_party/libvpx/
Patch Set: Created 6 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « source/libvpx/vp9/encoder/vp9_firstpass.h ('k') | source/libvpx/vp9/encoder/vp9_mbgraph.c » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: source/libvpx/vp9/encoder/vp9_firstpass.c
===================================================================
--- source/libvpx/vp9/encoder/vp9_firstpass.c (revision 292608)
+++ source/libvpx/vp9/encoder/vp9_firstpass.c (working copy)
@@ -38,14 +38,16 @@
#define OUTPUT_FPF 0
#define ARF_STATS_OUTPUT 0
+#define BOOST_BREAKOUT 12.5
#define BOOST_FACTOR 12.5
-#define ERR_DIVISOR 100.0
-#define FACTOR_PT_LOW 0.5
-#define FACTOR_PT_HIGH 0.9
+#define ERR_DIVISOR 128.0
+#define FACTOR_PT_LOW 0.70
+#define FACTOR_PT_HIGH 0.90
#define FIRST_PASS_Q 10.0
#define GF_MAX_BOOST 96.0
#define INTRA_MODE_PENALTY 1024
#define KF_MAX_BOOST 128.0
+#define MIN_ARF_GF_BOOST 240
#define MIN_DECAY_FACTOR 0.01
#define MIN_GF_INTERVAL 4
#define MIN_KF_BOOST 300
@@ -64,13 +66,6 @@
*b = temp;
}
-static int gfboost_qadjust(int qindex, vpx_bit_depth_t bit_depth) {
- const double q = vp9_convert_qindex_to_q(qindex, bit_depth);
- return (int)((0.00000828 * q * q * q) +
- (-0.0055 * q * q) +
- (1.32 * q) + 79.3);
-}
-
// Resets the first pass file to the given position using a relative seek from
// the current position.
static void reset_fpf_position(TWO_PASS *p,
@@ -281,6 +276,60 @@
return sse;
}
+#if CONFIG_VP9_HIGHBITDEPTH
+static vp9_variance_fn_t highbd_get_block_variance_fn(BLOCK_SIZE bsize,
+ int bd) {
+ switch (bd) {
+ default:
+ switch (bsize) {
+ case BLOCK_8X8:
+ return vp9_highbd_mse8x8;
+ case BLOCK_16X8:
+ return vp9_highbd_mse16x8;
+ case BLOCK_8X16:
+ return vp9_highbd_mse8x16;
+ default:
+ return vp9_highbd_mse16x16;
+ }
+ break;
+ case 10:
+ switch (bsize) {
+ case BLOCK_8X8:
+ return vp9_highbd_10_mse8x8;
+ case BLOCK_16X8:
+ return vp9_highbd_10_mse16x8;
+ case BLOCK_8X16:
+ return vp9_highbd_10_mse8x16;
+ default:
+ return vp9_highbd_10_mse16x16;
+ }
+ break;
+ case 12:
+ switch (bsize) {
+ case BLOCK_8X8:
+ return vp9_highbd_12_mse8x8;
+ case BLOCK_16X8:
+ return vp9_highbd_12_mse16x8;
+ case BLOCK_8X16:
+ return vp9_highbd_12_mse8x16;
+ default:
+ return vp9_highbd_12_mse16x16;
+ }
+ break;
+ }
+}
+
+static unsigned int highbd_get_prediction_error(BLOCK_SIZE bsize,
+ const struct buf_2d *src,
+ const struct buf_2d *ref,
+ int bd) {
+ unsigned int sse;
+ const vp9_variance_fn_t fn = highbd_get_block_variance_fn(bsize, bd);
+ fn(src->buf, src->stride, ref->buf, ref->stride, &sse);
+ return sse;
+}
+#endif // CONFIG_VP9_HIGHBITDEPTH
+
// Refine the motion search range according to the frame dimension
// for first pass test.
static int get_search_range(const VP9_COMMON *cm) {
@@ -311,6 +360,11 @@
// Override the default variance function to use MSE.
v_fn_ptr.vf = get_block_variance_fn(bsize);
+#if CONFIG_VP9_HIGHBITDEPTH
+ if (xd->cur_buf->flags & YV12_FLAG_HIGHBITDEPTH) {
+ v_fn_ptr.vf = highbd_get_block_variance_fn(bsize, xd->bd);
+ }
+#endif // CONFIG_VP9_HIGHBITDEPTH
// Center the initial step/diamond search on best mv.
tmp_err = cpi->diamond_search_sad(x, &cpi->ss_cfg, &ref_mv_full, &tmp_mv,
@@ -562,6 +616,24 @@
(bsize >= BLOCK_16X16 ? TX_16X16 : TX_8X8) : TX_4X4;
vp9_encode_intra_block_plane(x, bsize, 0);
this_error = vp9_get_mb_ss(x->plane[0].src_diff);
+#if CONFIG_VP9_HIGHBITDEPTH
+ if (cm->use_highbitdepth) {
+ switch (cm->bit_depth) {
+ case VPX_BITS_8:
+ break;
+ case VPX_BITS_10:
+ this_error >>= 4;
+ break;
+ case VPX_BITS_12:
+ this_error >>= 8;
+ break;
+ default:
+ assert(0 && "cm->bit_depth should be VPX_BITS_8, "
+ "VPX_BITS_10 or VPX_BITS_12");
+ return;
+ }
+ }
+#endif // CONFIG_VP9_HIGHBITDEPTH
if (cpi->oxcf.aq_mode == VARIANCE_AQ) {
vp9_clear_system_state();
@@ -601,8 +673,18 @@
struct buf_2d unscaled_last_source_buf_2d;
xd->plane[0].pre[0].buf = first_ref_buf->y_buffer + recon_yoffset;
- motion_error = get_prediction_error(bsize, &x->plane[0].src,
- &xd->plane[0].pre[0]);
+#if CONFIG_VP9_HIGHBITDEPTH
+ if (xd->cur_buf->flags & YV12_FLAG_HIGHBITDEPTH) {
+ motion_error = highbd_get_prediction_error(
+ bsize, &x->plane[0].src, &xd->plane[0].pre[0], xd->bd);
+ } else {
+ motion_error = get_prediction_error(
+ bsize, &x->plane[0].src, &xd->plane[0].pre[0]);
+ }
+#else
+ motion_error = get_prediction_error(
+ bsize, &x->plane[0].src, &xd->plane[0].pre[0]);
+#endif // CONFIG_VP9_HIGHBITDEPTH
// Compute the motion error of the 0,0 motion using the last source
// frame as the reference. Skip the further motion search on
@@ -611,8 +693,18 @@
cpi->unscaled_last_source->y_buffer + recon_yoffset;
unscaled_last_source_buf_2d.stride =
cpi->unscaled_last_source->y_stride;
- raw_motion_error = get_prediction_error(bsize, &x->plane[0].src,
- &unscaled_last_source_buf_2d);
+#if CONFIG_VP9_HIGHBITDEPTH
+ if (xd->cur_buf->flags & YV12_FLAG_HIGHBITDEPTH) {
+ raw_motion_error = highbd_get_prediction_error(
+ bsize, &x->plane[0].src, &unscaled_last_source_buf_2d, xd->bd);
+ } else {
+ raw_motion_error = get_prediction_error(
+ bsize, &x->plane[0].src, &unscaled_last_source_buf_2d);
+ }
+#else
+ raw_motion_error = get_prediction_error(
+ bsize, &x->plane[0].src, &unscaled_last_source_buf_2d);
+#endif // CONFIG_VP9_HIGHBITDEPTH
// TODO(pengchong): Replace the hard-coded threshold
if (raw_motion_error > 25 || lc != NULL) {
@@ -648,8 +740,18 @@
int gf_motion_error;
xd->plane[0].pre[0].buf = gld_yv12->y_buffer + recon_yoffset;
- gf_motion_error = get_prediction_error(bsize, &x->plane[0].src,
- &xd->plane[0].pre[0]);
+#if CONFIG_VP9_HIGHBITDEPTH
+ if (xd->cur_buf->flags & YV12_FLAG_HIGHBITDEPTH) {
+ gf_motion_error = highbd_get_prediction_error(
+ bsize, &x->plane[0].src, &xd->plane[0].pre[0], xd->bd);
+ } else {
+ gf_motion_error = get_prediction_error(
+ bsize, &x->plane[0].src, &xd->plane[0].pre[0]);
+ }
+#else
+ gf_motion_error = get_prediction_error(
+ bsize, &x->plane[0].src, &xd->plane[0].pre[0]);
+#endif // CONFIG_VP9_HIGHBITDEPTH
first_pass_motion_search(cpi, x, &zero_mv, &tmp_mv,
&gf_motion_error);
@@ -949,7 +1051,7 @@
// Adjustment based on actual quantizer to power term.
const double power_term =
- MIN(vp9_convert_qindex_to_q(q, bit_depth) * 0.0125 + pt_low, pt_high);
+ MIN(vp9_convert_qindex_to_q(q, bit_depth) * 0.01 + pt_low, pt_high);
// Calculate correction factor.
if (power_term < 1.0)
@@ -958,6 +1060,11 @@
return fclamp(pow(error_term, power_term), 0.05, 5.0);
}
+// Larger image formats are expected to be a little harder to code relatively
+// given the same prediction error score. This in part at least relates to the
+// increased size and hence coding cost of motion vectors.
+#define EDIV_SIZE_FACTOR 800
+
static int get_twopass_worst_quality(const VP9_COMP *cpi,
const FIRSTPASS_STATS *stats,
int section_target_bandwidth) {
@@ -971,8 +1078,10 @@
const double section_err = stats->coded_error / stats->count;
const double err_per_mb = section_err / num_mbs;
const double speed_term = 1.0 + 0.04 * oxcf->speed;
+ const double ediv_size_correction = num_mbs / EDIV_SIZE_FACTOR;
const int target_norm_bits_per_mb = ((uint64_t)section_target_bandwidth <<
BPER_MB_NORMBITS) / num_mbs;
+
int q;
int is_svc_upper_layer = 0;
if (is_two_pass_svc(cpi) && cpi->svc.spatial_layer_id > 0)
@@ -982,7 +1091,7 @@
// content at the given rate.
for (q = rc->best_quality; q < rc->worst_quality; ++q) {
const double factor =
- calc_correction_factor(err_per_mb, ERR_DIVISOR,
+ calc_correction_factor(err_per_mb, ERR_DIVISOR - ediv_size_correction,
is_svc_upper_layer ? SVC_FACTOR_PT_LOW :
FACTOR_PT_LOW, FACTOR_PT_HIGH, q,
cpi->common.bit_depth);
@@ -1065,6 +1174,8 @@
// Reset the vbr bits off target counter
cpi->rc.vbr_bits_off_target = 0;
+ cpi->rc.rate_error_estimate = 0;
+
// Static sequence monitor variables.
twopass->kf_zeromotion_pct = 100;
twopass->last_kfgroup_zeromotion_pct = 100;
@@ -1199,11 +1310,15 @@
double this_frame_mv_in_out,
double max_boost) {
double frame_boost;
+ const double lq =
+ vp9_convert_qindex_to_q(cpi->rc.avg_frame_qindex[INTER_FRAME],
+ cpi->common.bit_depth);
+ const double boost_correction = MIN((0.5 + (lq * 0.015)), 1.5);
// Underlying boost factor is based on inter error ratio.
frame_boost = (BASELINE_ERR_PER_MB * cpi->common.MBs) /
DOUBLE_DIVIDE_CHECK(this_frame->coded_error);
- frame_boost = frame_boost * BOOST_FACTOR;
+ frame_boost = frame_boost * BOOST_FACTOR * boost_correction;
// Increase boost for frames where new data coming into frame (e.g. zoom out).
// Slightly reduce boost if there is a net balance of motion out of the frame
@@ -1214,7 +1329,7 @@
else
frame_boost += frame_boost * (this_frame_mv_in_out / 2.0);
- return MIN(frame_boost, max_boost);
+ return MIN(frame_boost, max_boost * boost_correction);
}
static int calc_arf_boost(VP9_COMP *cpi, int offset,
@@ -1303,6 +1418,7 @@
arf_boost = (*f_boost + *b_boost);
if (arf_boost < ((b_frames + f_frames) * 20))
arf_boost = ((b_frames + f_frames) * 20);
+ arf_boost = MAX(arf_boost, MIN_ARF_GF_BOOST);
return arf_boost;
}
@@ -1580,6 +1696,7 @@
int b_boost = 0;
int flash_detected;
int active_max_gf_interval;
+ int active_min_gf_interval;
int64_t gf_group_bits;
double gf_group_error_left;
int gf_arf_bits;
@@ -1608,21 +1725,30 @@
// Motion breakout threshold for loop below depends on image size.
mv_ratio_accumulator_thresh = (cpi->common.width + cpi->common.height) / 4.0;
- // Work out a maximum interval for the GF group.
+ // Set a maximum and minimum interval for the GF group.
// If the image appears almost completely static we can extend beyond this.
- if (cpi->multi_arf_allowed) {
- active_max_gf_interval = rc->max_gf_interval;
- } else {
- // The value chosen depends on the active Q range. At low Q we have
- // bits to spare and are better with a smaller interval and smaller boost.
- // At high Q when there are few bits to spare we are better with a longer
- // interval to spread the cost of the GF.
- active_max_gf_interval =
- 12 + ((int)vp9_convert_qindex_to_q(rc->last_q[INTER_FRAME],
- cpi->common.bit_depth) >> 5);
+ {
+ int int_max_q =
+ (int)(vp9_convert_qindex_to_q(twopass->active_worst_quality,
+ cpi->common.bit_depth));
+ int int_lbq =
+ (int)(vp9_convert_qindex_to_q(rc->last_boosted_qindex,
+ cpi->common.bit_depth));
+ active_min_gf_interval = MIN_GF_INTERVAL + MIN(2, int_max_q / 200);
+ if (active_min_gf_interval > rc->max_gf_interval)
+ active_min_gf_interval = rc->max_gf_interval;
- if (active_max_gf_interval > rc->max_gf_interval)
- active_max_gf_interval = rc->max_gf_interval;
+ if (cpi->multi_arf_allowed) {
+ active_max_gf_interval = rc->max_gf_interval;
+ } else {
+ // The value chosen depends on the active Q range. At low Q we have
+ // bits to spare and are better with a smaller interval and smaller boost.
+ // At high Q when there are few bits to spare we are better with a longer
+ // interval to spread the cost of the GF.
+ active_max_gf_interval = 12 + MIN(4, (int_lbq / 6));
+ if (active_max_gf_interval > rc->max_gf_interval)
+ active_max_gf_interval = rc->max_gf_interval;
+ }
}
i = 0;
@@ -1678,12 +1804,12 @@
(i >= active_max_gf_interval && (zero_motion_accumulator < 0.995)) ||
(
// Don't break out with a very short interval.
- (i > MIN_GF_INTERVAL) &&
+ (i > active_min_gf_interval) &&
(!flash_detected) &&
((mv_ratio_accumulator > mv_ratio_accumulator_thresh) ||
(abs_mv_in_out_accumulator > 3.0) ||
(mv_in_out_accumulator < -2.0) ||
- ((boost_score - old_boost_score) < BOOST_FACTOR)))) {
+ ((boost_score - old_boost_score) < BOOST_BREAKOUT)))) {
boost_score = old_boost_score;
break;
}
@@ -1731,7 +1857,7 @@
(cpi->multi_arf_allowed && (rc->baseline_gf_interval >= 6) &&
(zero_motion_accumulator < 0.995)) ? 1 : 0;
} else {
- rc->gfu_boost = MAX((int)boost_score, 125);
+ rc->gfu_boost = MAX((int)boost_score, MIN_ARF_GF_BOOST);
rc->source_alt_ref_pending = 0;
}
@@ -1742,19 +1868,9 @@
gf_group_bits = calculate_total_gf_group_bits(cpi, gf_group_err);
// Calculate the extra bits to be used for boosted frame(s)
- {
- int q = rc->last_q[INTER_FRAME];
- int boost =
- (rc->gfu_boost * gfboost_qadjust(q, cpi->common.bit_depth)) / 100;
+ gf_arf_bits = calculate_boost_bits(rc->baseline_gf_interval,
+ rc->gfu_boost, gf_group_bits);
- // Set max and minimum boost and hence minimum allocation.
- boost = clamp(boost, 125, (rc->baseline_gf_interval + 1) * 200);
-
- // Calculate the extra bits to be used for boosted frame(s)
- gf_arf_bits = calculate_boost_bits(rc->baseline_gf_interval,
- boost, gf_group_bits);
- }
-
// Adjust KF group bits and error remaining.
twopass->kf_group_error_left -= (int64_t)gf_group_err;
@@ -2101,11 +2217,24 @@
twopass->modified_error_left -= kf_group_err;
}
+#define VBR_PCT_ADJUSTMENT_LIMIT 50
// For VBR...adjustment to the frame target based on error from previous frames
-void vbr_rate_correction(int * this_frame_target,
+void vbr_rate_correction(VP9_COMP *cpi,
+ int * this_frame_target,
const int64_t vbr_bits_off_target) {
- int max_delta = (*this_frame_target * 15) / 100;
+ int max_delta;
+ double position_factor = 1.0;
+ // How far through the clip are we.
+ // This number is used to damp the per frame rate correction.
+ // Range 0 - 1.0
+ if (cpi->twopass.total_stats.count) {
+ position_factor = sqrt((double)cpi->common.current_video_frame /
+ cpi->twopass.total_stats.count);
+ }
+ max_delta = (int)(position_factor *
+ ((*this_frame_target * VBR_PCT_ADJUSTMENT_LIMIT) / 100));
+
// vbr_bits_off_target > 0 means we have extra bits to spend
if (vbr_bits_off_target > 0) {
*this_frame_target +=
@@ -2202,7 +2331,7 @@
// Correction to rate target based on prior over or under shoot.
if (cpi->oxcf.rc_mode == VPX_VBR)
- vbr_rate_correction(&target_rate, rc->vbr_bits_off_target);
+ vbr_rate_correction(cpi, &target_rate, rc->vbr_bits_off_target);
vp9_rc_set_frame_target(cpi, target_rate);
cm->frame_type = INTER_FRAME;
@@ -2234,7 +2363,11 @@
section_target_bandwidth);
twopass->active_worst_quality = tmp_q;
rc->ni_av_qi = tmp_q;
+ rc->last_q[INTER_FRAME] = tmp_q;
rc->avg_q = vp9_convert_qindex_to_q(tmp_q, cm->bit_depth);
+ rc->avg_frame_qindex[INTER_FRAME] = tmp_q;
+ rc->last_q[KEY_FRAME] = (tmp_q + cpi->oxcf.best_allowed_q) / 2;
+ rc->avg_frame_qindex[KEY_FRAME] = rc->last_q[KEY_FRAME];
}
vp9_zero(this_frame);
if (EOF == input_stats(twopass, &this_frame))
@@ -2259,6 +2392,9 @@
cpi->ref_frame_flags &=
(~VP9_LAST_FLAG & ~VP9_GOLD_FLAG & ~VP9_ALT_FLAG);
lc->frames_from_key_frame = 0;
+ // Reset the empty frame resolution since we have a key frame.
+ cpi->svc.empty_frame_width = cm->width;
+ cpi->svc.empty_frame_height = cm->height;
}
} else {
cm->frame_type = INTER_FRAME;
@@ -2275,16 +2411,6 @@
if (rc->frames_till_gf_update_due == 0) {
define_gf_group(cpi, &this_frame_copy);
- if (twopass->gf_zeromotion_pct > 995) {
- // As long as max_thresh for encode breakout is small enough, it is ok
- // to enable it for show frame, i.e. set allow_encode_breakout to
- // ENCODE_BREAKOUT_LIMITED.
- if (!cm->show_frame)
- cpi->allow_encode_breakout = ENCODE_BREAKOUT_DISABLED;
- else
- cpi->allow_encode_breakout = ENCODE_BREAKOUT_LIMITED;
- }
-
rc->frames_till_gf_update_due = rc->baseline_gf_interval;
if (lc != NULL)
cpi->refresh_golden_frame = 1;
@@ -2294,8 +2420,9 @@
FILE *fpfile;
fpfile = fopen("arf.stt", "a");
++arf_count;
- fprintf(fpfile, "%10d %10d %10d %10ld\n",
- cm->current_video_frame, rc->kf_boost, arf_count, rc->gfu_boost);
+ fprintf(fpfile, "%10d %10ld %10d %10d %10ld\n",
+ cm->current_video_frame, rc->frames_till_gf_update_due,
+ rc->kf_boost, arf_count, rc->gfu_boost);
fclose(fpfile);
}
@@ -2314,7 +2441,7 @@
// Correction to rate target based on prior over or under shoot.
if (cpi->oxcf.rc_mode == VPX_VBR)
- vbr_rate_correction(&target_rate, rc->vbr_bits_off_target);
+ vbr_rate_correction(cpi, &target_rate, rc->vbr_bits_off_target);
vp9_rc_set_frame_target(cpi, target_rate);
@@ -2322,9 +2449,12 @@
subtract_stats(&twopass->total_left_stats, &this_frame);
}
+#define MINQ_ADJ_LIMIT 32
+#define Q_LIMIT_STEP 1
void vp9_twopass_postencode_update(VP9_COMP *cpi) {
TWO_PASS *const twopass = &cpi->twopass;
RATE_CONTROL *const rc = &cpi->rc;
+ const int bits_used = rc->base_frame_target;
// VBR correction is done through rc->vbr_bits_off_target. Based on the
// sign of this value, a limited % adjustment is made to the target rate
@@ -2331,11 +2461,18 @@
// of subsequent frames, to try and push it back towards 0. This method
// is designed to prevent extreme behaviour at the end of a clip
// or group of frames.
- const int bits_used = rc->base_frame_target;
rc->vbr_bits_off_target += rc->base_frame_target - rc->projected_frame_size;
-
twopass->bits_left = MAX(twopass->bits_left - bits_used, 0);
+ // Calculate the pct rc error.
+ if (rc->total_actual_bits) {
+ rc->rate_error_estimate =
+ (int)((rc->vbr_bits_off_target * 100) / rc->total_actual_bits);
+ rc->rate_error_estimate = clamp(rc->rate_error_estimate, -100, 100);
+ } else {
+ rc->rate_error_estimate = 0;
+ }
+
if (cpi->common.frame_type != KEY_FRAME &&
!vp9_is_upper_layer_key_frame(cpi)) {
twopass->kf_group_bits -= bits_used;
@@ -2345,4 +2482,32 @@
// Increment the gf group index ready for the next frame.
++twopass->gf_group.index;
+
+ // If the rate control is drifting consider adjustment ot min or maxq.
+ // Only make adjustments on gf/arf
+ if ((cpi->oxcf.rc_mode == VPX_VBR) &&
+ (cpi->twopass.gf_zeromotion_pct < VLOW_MOTION_THRESHOLD) &&
+ !cpi->rc.is_src_frame_alt_ref) {
+ const int maxq_adj_limit =
+ rc->worst_quality - twopass->active_worst_quality;
+
+ // Undershoot.
+ if (rc->rate_error_estimate > cpi->oxcf.under_shoot_pct) {
+ --twopass->extend_maxq;
+ if (rc->rolling_target_bits >= rc->rolling_actual_bits)
+ twopass->extend_minq += Q_LIMIT_STEP;
+ // Overshoot.
+ } else if (rc->rate_error_estimate < -cpi->oxcf.over_shoot_pct) {
+ --twopass->extend_minq;
+ if (rc->rolling_target_bits < rc->rolling_actual_bits)
+ twopass->extend_maxq += Q_LIMIT_STEP;
+ } else {
+ if (rc->rolling_target_bits < rc->rolling_actual_bits)
+ --twopass->extend_minq;
+ if (rc->rolling_target_bits > rc->rolling_actual_bits)
+ --twopass->extend_maxq;
+ }
+ twopass->extend_minq = clamp(twopass->extend_minq, 0, MINQ_ADJ_LIMIT);
+ twopass->extend_maxq = clamp(twopass->extend_maxq, 0, maxq_adj_limit);
+ }
}
« no previous file with comments | « source/libvpx/vp9/encoder/vp9_firstpass.h ('k') | source/libvpx/vp9/encoder/vp9_mbgraph.c » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698