From ea21f718104950dd2cb18af8e7304196eddbebfc Mon Sep 17 00:00:00 2001 From: Logaprakash Ramajayam Date: Thu, 25 Jun 2026 06:52:14 +0530 Subject: [PATCH 01/23] Update Bilateralfilter in MCSTF --- source/common/temporalfilter.cpp | 54 +++++++++++++++----------------- source/common/temporalfilter.h | 2 +- source/encoder/slicetype.cpp | 20 ++---------- 3 files changed, 28 insertions(+), 48 deletions(-) diff --git a/source/common/temporalfilter.cpp b/source/common/temporalfilter.cpp index e26628db2..ee40c91f4 100644 --- a/source/common/temporalfilter.cpp +++ b/source/common/temporalfilter.cpp @@ -511,15 +511,7 @@ void TemporalFilter::bilateralFilter(Frame* frame, applyMotion(m_mcstfRefList[i].mvs, m_mcstfRefList[i].mvsStride, m_mcstfRefList[i].picBuffer, ref->compensatedPic); } - int refStrengthRow = 2; - if (numRefs == m_range * 2) - { - refStrengthRow = 0; - } - else if (numRefs == m_range) - { - refStrengthRow = 1; - } + int refStrengthRow = 0; const double lumaSigmaSq = (m_QP - m_sigmaZeroPoint) * (m_QP - m_sigmaZeroPoint) * m_sigmaMultiplier; const double chromaSigmaSq = 30 * 30; @@ -578,34 +570,38 @@ void TemporalFilter::bilateralFilter(Frame* frame, correctedPicsStride = refPicInfo->compensatedPic->m_stride; else correctedPicsStride = refPicInfo->compensatedPic->m_strideC; - - const intptr_t pelOffset = y * correctedPicsStride + x; - primitives.pu[1].copy_pp(m_metld->me.fencPUYuv.m_buf[0], FENC_STRIDE, refPicInfo->compensatedPic->m_picOrg[c] + pelOffset, correctedPicsStride); - + const pixel *refPel = refPicInfo->compensatedPic->m_picOrg[c] +y * correctedPicsStride + x; + double variance = 0, diffsum = 0; - for (int y1 = 0; y1 < blkSize - 1; y1++) + for (int y1 = 0; y1 < blkSize; y1++) { - for (int x1 = 0; x1 < blkSize - 1; x1++) + for (int x1 = 0; x1 < blkSize; x1++) { - int pix = *(srcPel + x1); - int pixR = *(srcPel + x1 + 1); - int pixD = *(srcPel + x1 + srcStride); - - int ref = *(m_metld->me.fencPUYuv.m_buf[0] + ((y1)*FENC_STRIDE + x1)); - int refR = *(m_metld->me.fencPUYuv.m_buf[0] + ((y1)*FENC_STRIDE + x1 + 1)); - int refD = *(m_metld->me.fencPUYuv.m_buf[0] + ((y1 + 1) * FENC_STRIDE + x1)); - + int pix = *(srcPel + srcStride * y1 + x1); + int ref = *(refPel + correctedPicsStride * y1 + x1); int diff = pix - ref; - int diffR = pixR - refR; - int diffD = pixD - refD; variance += diff * diff; - diffsum += (diffR - diff) * (diffR - diff); - diffsum += (diffD - diff) * (diffD - diff); + + if (x1 != blkSize - 1) + { + int pixR = *(srcPel + srcStride * y1 + x1 + 1); + int refR = *(refPel + correctedPicsStride * y1 + x1 + 1); + int diffR = pixR - refR; + diffsum += (diffR - diff) * (diffR - diff); + } + if (y1 != blkSize - 1) + { + int pixD = *(srcPel + srcStride * y1 + x1 + srcStride); + int refD = *(refPel + correctedPicsStride * y1 + x1 + correctedPicsStride); + int diffD = pixD - refD; + diffsum += (diffD - diff) * (diffD - diff); + } } } - - refPicInfo->noise[(y / blkSize) * refPicInfo->mvsStride + (x / blkSize)] = (int)round((300 * variance + 50) / (10 * diffsum + 50)); + const int cntV = blkSize * blkSize; + const int cntD = 2 * cntV - blkSize - blkSize; + refPicInfo->noise[(y / blkSize) * refPicInfo->mvsStride + (x / blkSize)] = (int)round((15.0 * cntD / cntV * variance + 5.0) / (diffsum + 5.0)); } } diff --git a/source/common/temporalfilter.h b/source/common/temporalfilter.h index c8c226b7c..c10f0e3b5 100644 --- a/source/common/temporalfilter.h +++ b/source/common/temporalfilter.h @@ -85,7 +85,7 @@ namespace X265_NS { me.init(X265_CSP_I400); me.setQP(X265_LOOKAHEAD_QP); predPUYuv.create(FENC_STRIDE, X265_CSP_I400); - m_useSADinME = 1; + m_useSADinME = 0; m_motionVectorFactor = 16; } diff --git a/source/encoder/slicetype.cpp b/source/encoder/slicetype.cpp index 4d362112d..9c6a6ff54 100644 --- a/source/encoder/slicetype.cpp +++ b/source/encoder/slicetype.cpp @@ -1881,25 +1881,9 @@ bool Lookahead::generatemcstf(Frame * frameEnc, PicList refPic, int poclast) for (int j = 0; j < (2 * frameEnc->m_mcstf->m_range); j++) { if (iterPOC < 0) - continue; + continue; if (iterPOC >= poclast) - { - - TemporalFilter * mcstf = frameEnc->m_mcstf; - while (mcstf->m_numRef) - { - memset(frameEnc->m_mcstfRefList[mcstf->m_numRef].mvs0, 0, sizeof(MV) * ((mcstf->m_sourceWidth / 16) * (mcstf->m_sourceHeight / 16))); - memset(frameEnc->m_mcstfRefList[mcstf->m_numRef].mvs1, 0, sizeof(MV) * ((mcstf->m_sourceWidth / 16) * (mcstf->m_sourceHeight / 16))); - memset(frameEnc->m_mcstfRefList[mcstf->m_numRef].mvs2, 0, sizeof(MV) * ((mcstf->m_sourceWidth / 16) * (mcstf->m_sourceHeight / 16))); - memset(frameEnc->m_mcstfRefList[mcstf->m_numRef].mvs, 0, sizeof(MV) * ((mcstf->m_sourceWidth / 4) * (mcstf->m_sourceHeight / 4))); - memset(frameEnc->m_mcstfRefList[mcstf->m_numRef].noise, 0, sizeof(int) * ((mcstf->m_sourceWidth / 4) * (mcstf->m_sourceHeight / 4))); - memset(frameEnc->m_mcstfRefList[mcstf->m_numRef].error, 0, sizeof(int) * ((mcstf->m_sourceWidth / 4) * (mcstf->m_sourceHeight / 4))); - - mcstf->m_numRef--; - } - - break; - } + break; Frame * iterFrame = refPic.getPOCMCSTF(iterPOC); if (iterFrame->m_poc == iterPOC) { From 40c34c2670ef8abf17e262bf698e2e3a6e6a1065 Mon Sep 17 00:00:00 2001 From: Logaprakash Ramajayam Date: Thu, 25 Jun 2026 07:09:57 +0530 Subject: [PATCH 02/23] Add CLI: --mcstf-ref-range for param->mcstfFrameRange --- source/common/param.cpp | 6 ++++++ source/x265cli.cpp | 1 + source/x265cli.h | 1 + 3 files changed, 8 insertions(+) diff --git a/source/common/param.cpp b/source/common/param.cpp index c09214d37..244e4a06d 100755 --- a/source/common/param.cpp +++ b/source/common/param.cpp @@ -1500,6 +1500,7 @@ int x265_param_parse(x265_param* p, const char* name, const char* value) OPT("film-grain") p->filmGrain = (char* )value; OPT("aom-film-grain") p->aomFilmGrain = (char*)value; OPT("mcstf") p->bEnableTemporalFilter = atobool(value); + OPT("mcstf-ref-range") p->mcstfFrameRange = atoi(value); OPT("sbrc") p->bEnableSBRC = atobool(value); #if ENABLE_ALPHA OPT("alpha") @@ -2229,7 +2230,10 @@ void x265_print_params(x265_param* param) TOOLOPT(param->toneMapFile != NULL, "dhdr10-info"); #endif if(param->bEnableTemporalFilter) + { TOOLOPT(param->bEnableTemporalFilter, "mcstf"); + TOOLVAL(param->mcstfFrameRange, "mcstf-ref-range=%d"); + } x265_log(param, X265_LOG_INFO, "tools:%s\n", buf); fflush(stderr); } @@ -2493,6 +2497,8 @@ char *x265_param2string(x265_param* p, int padx, int pady) if (p->aomFilmGrain) s += snprintf(s, bufSize - (s - buf), " aom-film-grain=%s", p->aomFilmGrain); BOOL(p->bEnableTemporalFilter, "mcstf"); + if (p->bEnableTemporalFilter) + s += snprintf(s, bufSize - (s - buf), " mcstf-ref-range=%d", p->mcstfFrameRange); #if ENABLE_ALPHA BOOL(p->bEnableAlpha, "alpha"); #endif diff --git a/source/x265cli.cpp b/source/x265cli.cpp index 9ec74c83a..bcb50f5aa 100755 --- a/source/x265cli.cpp +++ b/source/x265cli.cpp @@ -390,6 +390,7 @@ namespace X265_NS { H0(" --[no-]frame-dup Enable Frame duplication. Default %s\n", OPT(param->bEnableFrameDuplication)); H0(" --dup-threshold PSNR threshold for Frame duplication. Default %d\n", param->dupThreshold); H0(" --[no-]mcstf Enable GOP-based temporal filter. Default %d\n", param->bEnableTemporalFilter); + H0(" --mcstf-ref-range <0..4> Maximum number of range for MCSTF. Default %d\n", param->mcstfFrameRange); #if ENABLE_ALPHA H0(" --alpha Enable alpha channel support. Default %d\n", param->bEnableAlpha); #endif diff --git a/source/x265cli.h b/source/x265cli.h index b41d73748..81315ebd2 100644 --- a/source/x265cli.h +++ b/source/x265cli.h @@ -358,6 +358,7 @@ static const struct option long_options[] = { "dup-threshold", required_argument, NULL, 0 }, { "mcstf", no_argument, NULL, 0 }, { "no-mcstf", no_argument, NULL, 0 }, + { "mcstf-ref-range", required_argument, NULL, 0 }, #if ENABLE_ALPHA { "alpha", no_argument, NULL, 0 }, #endif From 7f13246704cffbd67468e137582b87dd28e6de24 Mon Sep 17 00:00:00 2001 From: Logaprakash Ramajayam Date: Thu, 25 Jun 2026 10:03:36 +0530 Subject: [PATCH 03/23] Implement Multi-threaded workflow for bilateralFilter in MCSTF --- source/common/temporalfilter.cpp | 227 +++++++++++++++++++------------ source/common/temporalfilter.h | 81 ++++++++++- source/encoder/frameencoder.cpp | 4 +- 3 files changed, 222 insertions(+), 90 deletions(-) diff --git a/source/common/temporalfilter.cpp b/source/common/temporalfilter.cpp index ee40c91f4..96956443f 100644 --- a/source/common/temporalfilter.cpp +++ b/source/common/temporalfilter.cpp @@ -23,7 +23,7 @@ #include "common.h" #include "temporalfilter.h" #include "primitives.h" - +#include "threadpool.h" #include "frame.h" #include "slice.h" #include "framedata.h" @@ -404,7 +404,7 @@ int MotionEstimatorTLD::motionErrorLumaSSD(MotionEstimatorTLD& m_metld, return error; } -void TemporalFilter::applyMotion(MV *mvs, uint32_t mvsStride, PicYuv *input, PicYuv *output) +void TemporalFilter::applyMotion(MV *mvs, uint32_t mvsStride, PicYuv *input, PicYuv *output, const int blockRow, const int rowSize) { static const int lumaBlockSize = 8; int srcStride = 0; @@ -434,7 +434,13 @@ void TemporalFilter::applyMotion(MV *mvs, uint32_t mvsStride, PicYuv *input, Pic const int height = input->m_picHeight >> csy; const int width = input->m_picWidth >> csx; - for (int y = 0, blockNumY = 0; y + blockSizeY <= height; y += blockSizeY, blockNumY++) + const int vShift = (!c) ? 0 : csy; + const int blkRowStart = (!rowSize) ? 0 : (blockRow * rowSize) >> vShift; + const int blkRowEnd = (!rowSize) ? height : X265_MIN((blockRow * rowSize + rowSize) >> vShift, height); + + int blockNumY = (!rowSize) ? 0 : blkRowStart / blockSizeY; + + for (int y = blkRowStart; y + blockSizeY <= blkRowEnd; y += blockSizeY, blockNumY++) { for (int x = 0, blockNumX = 0; x + blockSizeX <= width; x += blockSizeX, blockNumX++) { @@ -498,19 +504,9 @@ void TemporalFilter::applyMotion(MV *mvs, uint32_t mvsStride, PicYuv *input, Pic } } -void TemporalFilter::bilateralFilter(Frame* frame, - TemporalFilterRefPicInfo* m_mcstfRefList, - double overallStrength) +void TemporalFilter::bilateralFilter_core(Frame* frame, TemporalFilterRefPicInfo* m_mcstfRefList, int numRefs, int blockRow, int rowSize, double overallStrength) { - const int numRefs = frame->m_mcstf->m_numRef; - - for (int i = 0; i < numRefs; i++) - { - TemporalFilterRefPicInfo *ref = &m_mcstfRefList[i]; - applyMotion(m_mcstfRefList[i].mvs, m_mcstfRefList[i].mvsStride, m_mcstfRefList[i].picBuffer, ref->compensatedPic); - } - int refStrengthRow = 0; const double lumaSigmaSq = (m_QP - m_sigmaZeroPoint) * (m_QP - m_sigmaZeroPoint) * m_sigmaMultiplier; @@ -518,27 +514,33 @@ void TemporalFilter::bilateralFilter(Frame* frame, PicYuv* orgPic = frame->m_fencPic; + for (int i = 0; i < numRefs; i++) + { + TemporalFilterRefPicInfo *ref = &m_mcstfRefList[i]; + applyMotion(m_mcstfRefList[i].mvs, m_mcstfRefList[i].mvsStride, m_mcstfRefList[i].picBuffer, ref->compensatedPic, blockRow, rowSize); + } + for (int c = 0; c < m_numComponents; c++) { int height, width; - pixel *srcPelRow = NULL; + pixel *srcPelPlane = NULL; intptr_t srcStride, correctedPicsStride = 0; + int csx = (!c) ? 0 : CHROMA_H_SHIFT(m_internalCsp); + int csy = (!c) ? 0 : CHROMA_V_SHIFT(m_internalCsp); if (!c) { height = orgPic->m_picHeight; width = orgPic->m_picWidth; - srcPelRow = orgPic->m_picOrg[c]; + srcPelPlane = orgPic->m_picOrg[c]; srcStride = orgPic->m_stride; } else { - int csx = CHROMA_H_SHIFT(m_internalCsp); - int csy = CHROMA_V_SHIFT(m_internalCsp); height = orgPic->m_picHeight >> csy; width = orgPic->m_picWidth >> csx; - srcPelRow = orgPic->m_picOrg[c]; + srcPelPlane = orgPic->m_picOrg[c]; srcStride = (int)orgPic->m_strideC; } @@ -550,102 +552,155 @@ void TemporalFilter::bilateralFilter(Frame* frame, const int blkSize = (!c) ? 8 : 4; - for (int y = 0; y < height; y++, srcPelRow += srcStride) - { - pixel *srcPel = srcPelRow; + const int vShift = (!c) ? 0 : csy; + const int planeRowStart = (blockRow * rowSize) >> vShift; + const int planeRowEnd = X265_MIN((blockRow * rowSize + rowSize) >> vShift, height); + + const int blkRowStart = (planeRowStart / blkSize) * blkSize; + const int blkRowEnd = X265_MIN(((planeRowEnd + blkSize - 1) / blkSize) * blkSize, height); - for (int x = 0; x < width; x++, srcPel++) + for (int by = blkRowStart; by + blkSize <= blkRowEnd; by += blkSize) + { + for (int bx = 0; bx + blkSize <= width; bx += blkSize) { - const int orgVal = (int)*srcPel; - double temporalWeightSum = 1.0; - double newVal = (double)orgVal; + double vww [16] = {}; + double vsw [16] = {}; + + const pixel* srcPel = srcPelPlane + by * srcStride + bx; + + double minError = 9999999; - if ((y % blkSize == 0) && (x % blkSize == 0)) + for (int i = 0; i < numRefs; i++) { - for (int i = 0; i < numRefs; i++) + TemporalFilterRefPicInfo *refPicInfo = &m_mcstfRefList[i]; + + if (!c) + correctedPicsStride = refPicInfo->compensatedPic->m_stride; + else + correctedPicsStride = refPicInfo->compensatedPic->m_strideC; + + double variance = 0, diffsum = 0; + const pixel *refPel = refPicInfo->compensatedPic->m_picOrg[c] + by * correctedPicsStride + bx; + for (int y1 = 0; y1 < blkSize; y1++) { - TemporalFilterRefPicInfo *refPicInfo = &m_mcstfRefList[i]; - - if (!c) - correctedPicsStride = refPicInfo->compensatedPic->m_stride; - else - correctedPicsStride = refPicInfo->compensatedPic->m_strideC; - const pixel *refPel = refPicInfo->compensatedPic->m_picOrg[c] +y * correctedPicsStride + x; - - double variance = 0, diffsum = 0; - for (int y1 = 0; y1 < blkSize; y1++) + for (int x1 = 0; x1 < blkSize; x1++) { - for (int x1 = 0; x1 < blkSize; x1++) + int pix = *(srcPel + srcStride * y1 + x1); + int ref = *(refPel + correctedPicsStride * y1 + x1); + int diff = pix - ref; + + variance += diff * diff; + + if (x1 != blkSize - 1) + { + int pixR = *(srcPel + srcStride * y1 + x1 + 1); + int refR = *(refPel + correctedPicsStride * y1 + x1 + 1); + int diffR = pixR - refR; + diffsum += (diffR - diff) * (diffR - diff); + } + if (y1 != blkSize - 1) { - int pix = *(srcPel + srcStride * y1 + x1); - int ref = *(refPel + correctedPicsStride * y1 + x1); - int diff = pix - ref; - - variance += diff * diff; - - if (x1 != blkSize - 1) - { - int pixR = *(srcPel + srcStride * y1 + x1 + 1); - int refR = *(refPel + correctedPicsStride * y1 + x1 + 1); - int diffR = pixR - refR; - diffsum += (diffR - diff) * (diffR - diff); - } - if (y1 != blkSize - 1) - { - int pixD = *(srcPel + srcStride * y1 + x1 + srcStride); - int refD = *(refPel + correctedPicsStride * y1 + x1 + correctedPicsStride); - int diffD = pixD - refD; - diffsum += (diffD - diff) * (diffD - diff); - } + int pixD = *(srcPel + srcStride * y1 + x1 + srcStride); + int refD = *(refPel + correctedPicsStride * y1 + x1 + correctedPicsStride); + int diffD = pixD - refD; + diffsum += (diffD - diff) * (diffD - diff); } } - const int cntV = blkSize * blkSize; - const int cntD = 2 * cntV - blkSize - blkSize; - refPicInfo->noise[(y / blkSize) * refPicInfo->mvsStride + (x / blkSize)] = (int)round((15.0 * cntD / cntV * variance + 5.0) / (diffsum + 5.0)); } + const int cntV = blkSize * blkSize; + const int cntD = 2 * cntV - blkSize - blkSize; + refPicInfo->noise[(by / blkSize) * refPicInfo->mvsStride + (bx / blkSize)] = (int)round((15.0 * cntD / cntV * variance + 5.0) / (diffsum + 5.0)); + minError = X265_MIN(minError, (double)refPicInfo->error[(by / blkSize) * refPicInfo->mvsStride + (bx / blkSize)]); } - double minError = 9999999; + // ── Step 2: pre-compute vww / vsw per reference (block-level) ─ for (int i = 0; i < numRefs; i++) { - TemporalFilterRefPicInfo *refPicInfo = &m_mcstfRefList[i]; - minError = X265_MIN(minError, (double)refPicInfo->error[(y / blkSize) * refPicInfo->mvsStride + (x / blkSize)]); - } + TemporalFilterRefPicInfo* refPicInfo = &m_mcstfRefList[i]; - for (int i = 0; i < numRefs; i++) - { - TemporalFilterRefPicInfo *refPicInfo = &m_mcstfRefList[i]; - - const int error = refPicInfo->error[(y / blkSize) * refPicInfo->mvsStride + (x / blkSize)]; - const int noise = refPicInfo->noise[(y / blkSize) * refPicInfo->mvsStride + (x / blkSize)]; - - const pixel *pCorrectedPelPtr = refPicInfo->compensatedPic->m_picOrg[c] + (y * correctedPicsStride + x); - const int refVal = (int)*pCorrectedPelPtr; - double diff = (double)(refVal - orgVal); - diff *= bitDepthDiffWeighting; - double diffSq = diff * diff; + const int error = refPicInfo->error[(by / blkSize) * refPicInfo->mvsStride + (bx / blkSize)]; + const int noise = refPicInfo->noise[(by / blkSize) * refPicInfo->mvsStride + (bx / blkSize)]; const int index = X265_MIN(3, std::abs(refPicInfo->origOffset) - 1); + double ww = 1, sw = 1; - ww *= (noise < 25) ? 1 : 1.2; + ww *= (noise < 25) ? 1 : 1.2; sw *= (noise < 25) ? 1.3 : 0.8; ww *= (error < 50) ? 1.2 : ((error > 100) ? 0.8 : 1); sw *= (error < 50) ? 1.3 : 1; ww *= ((minError + 1) / (error + 1)); - const double weight = weightScaling * s_refStrengths[refStrengthRow][index] * ww * exp(-diffSq / (2 * sw * sigmaSq)); - newVal += weight * refVal; - temporalWeightSum += weight; + vww[i] = weightScaling * s_refStrengths[refStrengthRow][index] * ww; + vsw[i] = 2 * sw * sigmaSq; } - newVal /= temporalWeightSum; - double sampleVal = round(newVal); - sampleVal = (sampleVal < 0 ? 0 : (sampleVal > maxSampleValue ? maxSampleValue : sampleVal)); - *srcPel = (pixel)sampleVal; + + // ── Step 3: pixel loop uses pre-computed vww / vsw ────── + for (int y = by; y < X265_MIN(by + blkSize, height); y++) + { + for (int x = bx; x < X265_MIN(bx + blkSize, width); x++) + { + const int orgVal = (int)srcPelPlane[y * srcStride + x]; + double temporalWeightSum = 1.0; + double newVal = (double)orgVal; + + for (int i = 0; i < numRefs; i++) + { + TemporalFilterRefPicInfo* refPicInfo = &m_mcstfRefList[i]; + + correctedPicsStride = (!c) ? refPicInfo->compensatedPic->m_stride + : refPicInfo->compensatedPic->m_strideC; + + const pixel* pCorrectedPelPtr = refPicInfo->compensatedPic->m_picOrg[c] + + y * correctedPicsStride + x; + const int refVal = (int)*pCorrectedPelPtr; + + double diff = (double)(refVal - orgVal); + diff *= bitDepthDiffWeighting; + double diffSq = diff * diff; + + const double weight = vww[i] * exp(-diffSq / vsw[i]); + + newVal += weight * refVal; + temporalWeightSum += weight; + } + + newVal /= temporalWeightSum; + double sampleVal = round(newVal); + sampleVal = (sampleVal < 0 ? 0 : (sampleVal > maxSampleValue ? maxSampleValue : sampleVal)); + srcPelPlane[y * srcStride + x] = (pixel)sampleVal; + } + } + } } } } +// Splits the frame into 64-row blocks, dispatches jobs to the threadpool via BilateralFilterGroup, then returns. +void TemporalFilter::bilateralFilter(Frame* curFrame, TemporalFilterRefPicInfo* mctfRefList, double overallStrength, ThreadPool* pool) +{ + const int numRef = curFrame->m_mcstf->m_numRef; + const int rowSize = 64; + const int frameHeight = curFrame->m_fencPic->m_picHeight; + const int numBlockRows = (frameHeight + rowSize - 1) / rowSize; + + if (numRef == 0) + return; + + if (!pool) + { + bilateralFilter_core(curFrame, mctfRefList, numRef, 0, 0, overallStrength); + return; + } + + BilateralFilterGroup filterGroup(*this, pool); + + for (int row = 0; row < numBlockRows; row++) + filterGroup.add(curFrame, mctfRefList, numRef, row, rowSize, overallStrength); + + filterGroup.finishBatch(); +} + void MotionEstimatorTLD::motionEstimationLuma(MotionEstimatorTLD& m_metld, MV *mvs, uint32_t mvStride, pixel* src,int stride, int height, int width, pixel* buf, int blockSize, int sRange, MV* previous, uint32_t prevMvStride, int factor) { diff --git a/source/common/temporalfilter.h b/source/common/temporalfilter.h index c10f0e3b5..bfd7ca1d2 100644 --- a/source/common/temporalfilter.h +++ b/source/common/temporalfilter.h @@ -30,6 +30,7 @@ #include "piclist.h" #include "yuv.h" #include "motion.h" +#include "threadpool.h" const int s_interpolationFilter[16][8] = { @@ -188,12 +189,88 @@ namespace X265_NS { int createRefPicInfo(TemporalFilterRefPicInfo* refFrame, x265_param* param); - void bilateralFilter(Frame* frame, TemporalFilterRefPicInfo* mctfRefList, double overallStrength); + void bilateralFilter(Frame* frame, + TemporalFilterRefPicInfo* mctfRefList, + double overallStrength, + ThreadPool* pool); + + void bilateralFilter_core(Frame* frame, + TemporalFilterRefPicInfo* mctfRefList, + int numRef, + int blockRow, + int blockSize, + double overallStrength); void destroyRefPicInfo(TemporalFilterRefPicInfo* curFrame); - void applyMotion(MV *mvs, uint32_t mvsStride, PicYuv *input, PicYuv *output); + void applyMotion(MV *mvs, uint32_t mvsStride, PicYuv *input, PicYuv *output, const int blockRow = 0, const int rowSize = 0); + + }; + + class BilateralFilterGroup : public BondedTaskGroup + { + public: + TemporalFilter& m_filter; + ThreadPool* m_pool; + + struct FilterJob + { + Frame* frame; + TemporalFilterRefPicInfo* mctfRefList; + int numRef; + int blockRow; + int rowSize; + double overallStrength; + }; + + static const int MAX_FILTER_JOBS = 512; + FilterJob m_jobs[MAX_FILTER_JOBS]; + int m_jobTotal; + int m_jobAcquired; + Lock m_lock; + + BilateralFilterGroup(TemporalFilter& f, ThreadPool* pool) + : m_filter(f), m_pool(pool), m_jobTotal(0), m_jobAcquired(0) {} + void add(Frame* frame, TemporalFilterRefPicInfo* mctfRefList, + int numRef, int blockRow, int rowSize, double strength) + { + X265_CHECK(m_jobTotal < MAX_FILTER_JOBS, + "BilateralFilterGroup overflow\n"); + FilterJob& j = m_jobs[m_jobTotal++]; + j.frame = frame; + j.mctfRefList = mctfRefList; + j.numRef = numRef; + j.blockRow = blockRow; + j.rowSize = rowSize; + j.overallStrength = strength; + } + + void finishBatch() + { + if (m_pool) + tryBondPeers(*m_pool, m_jobTotal); + processTasks(-1); + waitForExit(); + m_jobTotal = m_jobAcquired = 0; + } + + void processTasks(int /*workerThreadID*/) override + { + m_lock.acquire(); + while (m_jobAcquired < m_jobTotal) + { + const int i = m_jobAcquired++; + m_lock.release(); + + const FilterJob& j = m_jobs[i]; + m_filter.bilateralFilter_core(j.frame, j.mctfRefList, j.numRef, + j.blockRow, j.rowSize, + j.overallStrength); + m_lock.acquire(); + } + m_lock.release(); + } }; } #endif diff --git a/source/encoder/frameencoder.cpp b/source/encoder/frameencoder.cpp index 366c71a87..f02fb9a8a 100644 --- a/source/encoder/frameencoder.cpp +++ b/source/encoder/frameencoder.cpp @@ -660,10 +660,10 @@ void FrameEncoder::compressFrame(int layer) } } } - if (m_param->bEnableTemporalFilter) + if (m_param->bEnableTemporalFilter && m_top->isFilterThisframe(m_frame[layer]->m_mcstf->m_sliceTypeConfig, m_frame[layer]->m_lowres.sliceType)) { m_frame[layer]->m_mcstf->m_QP = qp; - m_frame[layer]->m_mcstf->bilateralFilter(m_frame[layer], m_frame[layer]->m_mcstfRefList, m_param->temporalFilterStrength); + m_frame[layer]->m_mcstf->bilateralFilter(m_frame[layer], m_frame[layer]->m_mcstfRefList, m_param->temporalFilterStrength, m_pool); } if (m_nr) From 7b8f7d314f3835e6ef9062d69742942a738b0e00 Mon Sep 17 00:00:00 2001 From: Logaprakash Ramajayam Date: Fri, 26 Jun 2026 14:23:00 +0530 Subject: [PATCH 04/23] Implement Multithreading for Motion Estimation in MCSTF --- source/common/temporalfilter.cpp | 42 +++++++-------- source/common/temporalfilter.h | 4 +- source/encoder/slicetype.cpp | 91 +++++++++++++++++++++----------- source/encoder/slicetype.h | 8 +-- 4 files changed, 86 insertions(+), 59 deletions(-) diff --git a/source/common/temporalfilter.cpp b/source/common/temporalfilter.cpp index 96956443f..32c8baba6 100644 --- a/source/common/temporalfilter.cpp +++ b/source/common/temporalfilter.cpp @@ -702,7 +702,7 @@ void TemporalFilter::bilateralFilter(Frame* curFrame, TemporalFilterRefPicInfo* } void MotionEstimatorTLD::motionEstimationLuma(MotionEstimatorTLD& m_metld, MV *mvs, uint32_t mvStride, pixel* src,int stride, int height, int width, pixel* buf, int blockSize, - int sRange, MV* previous, uint32_t prevMvStride, int factor) + int sRange, int row, int rowSize, MV* previous, uint32_t prevMvStride, int factor) { int range = sRange; @@ -712,10 +712,15 @@ void MotionEstimatorTLD::motionEstimationLuma(MotionEstimatorTLD& m_metld, MV *m const int origWidth = width; const int origHeight = height; + int rowStart = row * rowSize; + if (rowStart > height) + return; + + int rowEnd = (!rowSize) ? height : X265_MIN(rowStart + rowSize, height); int error; - for (int blockY = 0; blockY + blockSize <= origHeight; blockY += stepSize) + for (int blockY = rowStart; blockY + blockSize <= rowEnd; blockY += stepSize) { for (int blockX = 0; blockX + blockSize <= origWidth; blockX += stepSize) { @@ -790,22 +795,7 @@ void MotionEstimatorTLD::motionEstimationLuma(MotionEstimatorTLD& m_metld, MV *m } } - if (blockY > 0) - { - int idx = ((blockY - stepSize) / stepSize) * mvStride + (blockX / stepSize); - MV aboveMV = mvs[idx]; - - if (m_useSADinME) - error = motionErrorLumaSAD(m_metld, src, stride, buf, blockX, blockY, aboveMV.x, aboveMV.y, blockSize, leastError); - else - error = motionErrorLumaSSD(m_metld, src, stride, buf, blockX, blockY, aboveMV.x, aboveMV.y, blockSize, leastError); - - if (error < leastError) - { - best.set(aboveMV.x, aboveMV.y); - leastError = error; - } - } + /* Removed above block's Motion estimation dependency as the atomicity cost outweighs quality benefit */ if (blockX > 0) { @@ -856,7 +846,7 @@ void MotionEstimatorTLD::motionEstimationLuma(MotionEstimatorTLD& m_metld, MV *m void MotionEstimatorTLD::motionEstimationLumaDoubleRes(MotionEstimatorTLD& m_metld, MV *mvs, uint32_t mvStride, PicYuv *orig, PicYuv *buffer, int blockSize, - MV *previous, uint32_t prevMvStride, int factor, int* minError) + MV *previous, uint32_t prevMvStride, int factor, int* minError, int row, int rowSize) { int range = 0; @@ -864,12 +854,18 @@ void MotionEstimatorTLD::motionEstimationLumaDoubleRes(MotionEstimatorTLD& m_met const int stepSize = blockSize; - const int origWidth = orig->m_picWidth; + const int origWidth = orig->m_picWidth; const int origHeight = orig->m_picHeight; + int rowStart = row * rowSize; + + if (row * rowSize > origHeight) + return; // row beyond frame edge — nothing to do + + int rowEnd = (!rowSize) ? origHeight : X265_MIN(rowStart + rowSize, origHeight); int error; - for (int blockY = 0; blockY + blockSize <= origHeight; blockY += stepSize) + for (int blockY = rowStart; blockY + blockSize <= rowEnd; blockY += stepSize) { for (int blockX = 0; blockX + blockSize <= origWidth; blockX += stepSize) { @@ -964,8 +960,8 @@ void MotionEstimatorTLD::motionEstimationLumaDoubleRes(MotionEstimatorTLD& m_met } } - - if (blockY > 0) + /* Using Above block's Motion vector only when above block is available within the same thread */ + if (blockY != rowStart) { int idx = ((blockY - stepSize) / stepSize) * mvStride + (blockX / stepSize); MV aboveMV = mvs[idx]; diff --git a/source/common/temporalfilter.h b/source/common/temporalfilter.h index bfd7ca1d2..87eb07b2b 100644 --- a/source/common/temporalfilter.h +++ b/source/common/temporalfilter.h @@ -97,11 +97,11 @@ namespace X265_NS { void init(const x265_param* param); - void motionEstimationLuma(MotionEstimatorTLD& m_tld, MV* mvs, uint32_t mvStride, pixel* src, int stride, int height, int width, pixel* buf, int bs, int sRange, + void motionEstimationLuma(MotionEstimatorTLD& m_tld, MV* mvs, uint32_t mvStride, pixel* src, int stride, int height, int width, pixel* buf, int bs, int sRange, int row, int rowSize, MV* previous = 0, uint32_t prevmvStride = 0, int factor = 1); void motionEstimationLumaDoubleRes(MotionEstimatorTLD& m_tld, MV* mvs, uint32_t mvStride, PicYuv* orig, PicYuv* buffer, int blockSize, - MV* previous, uint32_t prevMvStride, int factor, int* minError); + MV* previous, uint32_t prevMvStride, int factor, int* minError, int row, int rowSize); int motionErrorLumaSSD(MotionEstimatorTLD& m_tld, pixel* src, int stride, diff --git a/source/encoder/slicetype.cpp b/source/encoder/slicetype.cpp index 9c6a6ff54..2956973f3 100644 --- a/source/encoder/slicetype.cpp +++ b/source/encoder/slicetype.cpp @@ -1817,19 +1817,6 @@ void Lookahead::compCostBref(Lowres **frames, int start, int end, int num) } } -void CostEstimateGroup::estimatelowresmotion(MotionEstimatorTLD& m_metld, Frame* curframe, int refId) -{ - m_metld.m_bitDepth = curframe->m_param->internalBitDepth; - TemporalFilterRefPicInfo* ref = &curframe->m_mcstfRefList[refId]; - - m_metld.motionEstimationLuma(m_metld, ref->mvs0, ref->mvsStride0, curframe->m_lowres.lowerResPlane[0], (int)(curframe->m_lowres.lumaStride / 2), (curframe->m_lowres.lines / 2), (curframe->m_lowres.width / 2), ref->lowerRes, 16, curframe->m_param->searchRangeForLayer2); - m_metld.motionEstimationLuma(m_metld, ref->mvs1, ref->mvsStride1, curframe->m_lowres.lowresPlane[0], (int)(curframe->m_lowres.lumaStride), (curframe->m_lowres.lines), (curframe->m_lowres.width), ref->lowres, 16, curframe->m_param->searchRangeForLayer1, ref->mvs0, ref->mvsStride0, 2); - m_metld.motionEstimationLuma(m_metld, ref->mvs2, ref->mvsStride2, curframe->m_fencPic->m_picOrg[0], (int)curframe->m_fencPic->m_stride, curframe->m_fencPic->m_picHeight, curframe->m_fencPic->m_picWidth, ref->picBuffer->m_picOrg[0], 16, curframe->m_param->searchRangeForLayer0, ref->mvs1, ref->mvsStride1, 2); - m_metld.motionEstimationLumaDoubleRes(m_metld, ref->mvs, ref->mvsStride, curframe->m_fencPic, ref->picBuffer, 8, ref->mvs2, ref->mvsStride2, 1, ref->error); - - curframe->m_lowres.lowresMcstfMvs[0][refId][0].x = 1; -} - inline int enqueueRefFrame(Frame* iterFrame, Frame* curFrame, bool isPreFiltered, int16_t i) { TemporalFilterRefPicInfo * temp = &curFrame->m_mcstfRefList[curFrame->m_mcstf->m_numRef]; @@ -2163,10 +2150,8 @@ void Lookahead::slicetypeDecide() if (m_bBatchMotionSearch && m_param->bEnableTemporalFilter) { - /* pre-calculate all motion searches, using many worker threads */ - CostEstimateGroup estGroup(*this, frames); - Frame* frameEnc = m_inputQueue.first(); m_inputLock.acquire(); + Frame* frameEnc = m_inputQueue.first(); for (int b = 0; b < m_inputQueue.size(); b++) { if (m_param->bEnableTemporalFilter && isFilterThisframe(frameEnc->m_mcstf->m_sliceTypeConfig, frameEnc->m_lowres.sliceType)) @@ -2177,16 +2162,28 @@ void Lookahead::slicetypeDecide() fflush(stderr); } - for (int j = 1; j <= frameEnc->m_mcstf->m_numRef; j++) + const int rowMELevels = 4; + const int rowSize = 16; + const int origHeight = frameEnc->m_fencPic->m_picHeight; + const int levelHeight[4] = {origHeight / 4, origHeight / 2, origHeight, origHeight}; + for(int i = 0; i < rowMELevels; i++) { - TemporalFilterRefPicInfo* ref = &frameEnc->m_mcstfRefList[j - 1]; - int i = ref->poc; + const int numBlockRows = (levelHeight[i] + rowSize - 1) / rowSize; + CostEstimateGroup estGroup(*this, frames); - /* Skip search if already done */ - if (frames[b + 1]->lowresMcstfMvs[0][j - 1][0].x != 0x7FFF) - continue; + for (int j = 1; j <= frameEnc->m_mcstf->m_numRef; j++) + { + TemporalFilterRefPicInfo* ref = &frameEnc->m_mcstfRefList[j - 1]; + int refpoc = ref->poc; + + /* Skip search if already done */ + if (frameEnc->m_lowres.lowresMcstfMvs[0][j - 1][0].x != 0x7FFF) + continue; - estGroup.add(j - 1, i, frameEnc->m_poc); + for (int row = 0; row < numBlockRows; row++) + estGroup.add_row(j - 1, refpoc, frameEnc->m_poc, row, i, frameEnc); + } + estGroup.finishBatch(); } } frameEnc = frameEnc->m_next; @@ -2195,7 +2192,6 @@ void Lookahead::slicetypeDecide() /* auto-disable after the first batch if pool is small */ m_bBatchMotionSearch &= m_pool->m_numWorkers >= 4; - estGroup.finishBatch(); } if (m_param->bEnableTemporalSubLayers > 2) @@ -4053,9 +4049,30 @@ void CostEstimateGroup::add(int p0, int p1, int b) m_batchMode = true; Estimate& e = m_estimates[m_jobTotal++]; - e.p0 = p0; - e.p1 = p1; - e.b = b; + e.p0 = p0; + e.p1 = p1; + e.b = b; + e.blockRow = -1; + e.MElevel = -1; + e.frame = NULL; + + if (m_jobTotal == MAX_BATCH_SIZE) + finishBatch(); +} + +void CostEstimateGroup::add_row(int refIdx, int poc, int curPoc, int blockRow, int level, Frame* frame) +{ + X265_CHECK(m_batchMode || !m_jobTotal, + "single CostEstimateGroup instance cannot mix batch modes\n"); + m_batchMode = true; + + Estimate& e = m_estimates[m_jobTotal++]; + e.p0 = refIdx; + e.p1 = poc; + e.b = curPoc; + e.blockRow = blockRow; + e.MElevel = level; + e.frame = frame; if (m_jobTotal == MAX_BATCH_SIZE) finishBatch(); @@ -4091,12 +4108,24 @@ void CostEstimateGroup::processTasks(int workerThreadID) ProfileScopeEvent(estCostSingle); Estimate& e = m_estimates[i]; - m_lookahead.m_inputLock.acquire(); - Frame* curFrame = m_lookahead.m_inputQueue.getPOC(e.b); - m_lookahead.m_inputLock.release(); + Frame* curFrame = e.frame; if (m_lookahead.m_param->bEnableTemporalFilter && curFrame && (curFrame->m_lowres.sliceType == X265_TYPE_IDR || curFrame->m_lowres.sliceType == X265_TYPE_I || curFrame->m_lowres.sliceType == X265_TYPE_P)) { - estimatelowresmotion(m_metld, curFrame, e.p0); + m_metld.m_bitDepth = curFrame->m_param->internalBitDepth; + TemporalFilterRefPicInfo* ref = &curFrame->m_mcstfRefList[e.p0]; + + if (e.MElevel == 0) + m_metld.motionEstimationLuma(m_metld, ref->mvs0, ref->mvsStride0, curFrame->m_lowres.lowerResPlane[0], (int)(curFrame->m_lowres.lumaStride / 2), (curFrame->m_lowres.lines / 2), (curFrame->m_lowres.width / 2), ref->lowerRes, 16, curFrame->m_param->searchRangeForLayer2, e.blockRow, 16); + else if (e.MElevel == 1) + m_metld.motionEstimationLuma(m_metld, ref->mvs1, ref->mvsStride1, curFrame->m_lowres.lowresPlane[0], (int)(curFrame->m_lowres.lumaStride), (curFrame->m_lowres.lines), (curFrame->m_lowres.width), ref->lowres, 16, curFrame->m_param->searchRangeForLayer1, e.blockRow, 16, ref->mvs0, ref->mvsStride0, 2); + else if (e.MElevel == 2) + m_metld.motionEstimationLuma(m_metld, ref->mvs2, ref->mvsStride2, curFrame->m_fencPic->m_picOrg[0], (int)curFrame->m_fencPic->m_stride, curFrame->m_fencPic->m_picHeight, curFrame->m_fencPic->m_picWidth, ref->picBuffer->m_picOrg[0], 16, curFrame->m_param->searchRangeForLayer0, e.blockRow, 16, ref->mvs1, ref->mvsStride1, 2); + else if (e.MElevel == 3) + { + m_metld.motionEstimationLumaDoubleRes(m_metld, ref->mvs, ref->mvsStride, curFrame->m_fencPic, ref->picBuffer, 8, ref->mvs2, ref->mvsStride2, 1, ref->error, e.blockRow, 16); + if (e.blockRow == (int)(curFrame->m_fencPic->m_picHeight - 15)/16 - 1) + curFrame->m_lowres.lowresMcstfMvs[0][e.p0][0].x = 1; + } } else estimateFrameCost(tld, e.p0, e.p1, e.b, false); diff --git a/source/encoder/slicetype.h b/source/encoder/slicetype.h index be6ac8112..52395a6d0 100644 --- a/source/encoder/slicetype.h +++ b/source/encoder/slicetype.h @@ -312,10 +312,14 @@ class CostEstimateGroup : public BondedTaskGroup enum { MAX_BATCH_SIZE = 512 }; struct Estimate { - int p0, b, p1; + int p0, b, p1; + int blockRow; + int MElevel; + Frame* frame; } m_estimates[MAX_BATCH_SIZE]; void add(int p0, int p1, int b); + void add_row(int refIdx, int poc, int curPoc, int blockRow, int level, Frame* frame); void finishBatch(); protected: @@ -327,8 +331,6 @@ class CostEstimateGroup : public BondedTaskGroup int64_t estimateFrameCost(LookaheadTLD& tld, int p0, int p1, int b, bool intraPenalty); void estimateCUCost(LookaheadTLD& tld, int cux, int cuy, int p0, int p1, int b, bool bDoSearch[2], bool lastRow, int slice, bool hme); - void estimatelowresmotion(MotionEstimatorTLD& m_metld, Frame* curframe, int refId); - CostEstimateGroup& operator=(const CostEstimateGroup&); }; From 4946fd3c98ad58329a3b26ac429887200c980011 Mon Sep 17 00:00:00 2001 From: gunasrij Date: Fri, 26 Jun 2026 19:12:11 +0530 Subject: [PATCH 05/23] mcstf: Add AVX2 implementation for ME kernels --- source/common/CMakeLists.txt | 3 +- source/common/primitives.cpp | 7 + source/common/temporalfilter.cpp | 178 ++++++------- source/common/temporalfilter.h | 22 ++ source/common/x86/temporalfilter_simd.cpp | 302 ++++++++++++++++++++++ 5 files changed, 411 insertions(+), 101 deletions(-) create mode 100644 source/common/x86/temporalfilter_simd.cpp diff --git a/source/common/CMakeLists.txt b/source/common/CMakeLists.txt index b17c0cd87..64fdf0a91 100644 --- a/source/common/CMakeLists.txt +++ b/source/common/CMakeLists.txt @@ -39,6 +39,7 @@ if(ENABLE_ASSEMBLY AND X86) # x64 implies SSE4, so only add /arch:SSE2 if building for Win32 set_source_files_properties(${SSE3} ${SSSE3} ${SSE41} PROPERTIES COMPILE_FLAGS "${WARNDISABLE} /arch:SSE2") endif() + set_source_files_properties(x86/temporalfilter_simd.cpp PROPERTIES COMPILE_FLAGS "${WARNDISABLE} /arch:AVX2") endif() if(GCC) if(CLANG) @@ -56,7 +57,7 @@ if(ENABLE_ASSEMBLY AND X86) endif() set(VEC_PRIMITIVES vec/vec-primitives.cpp ${PRIMITIVES}) source_group(Intrinsics FILES ${VEC_PRIMITIVES}) - set(C_SRCS asm-primitives.cpp pixel.h mc.h ipfilter8.h blockcopy8.h dct8.h loopfilter.h seaintegral.h) + set(C_SRCS asm-primitives.cpp pixel.h mc.h ipfilter8.h blockcopy8.h dct8.h loopfilter.h seaintegral.h temporalfilter_simd.cpp) set(A_SRCS pixel-a.asm const-a.asm cpu-a.asm ssd-a.asm mc-a.asm mc-a2.asm pixel-util8.asm blockcopy8.asm pixeladd8.asm dct8.asm seaintegral.asm) diff --git a/source/common/primitives.cpp b/source/common/primitives.cpp index 55f702c7b..927529130 100644 --- a/source/common/primitives.cpp +++ b/source/common/primitives.cpp @@ -23,6 +23,7 @@ #include "common.h" #include "primitives.h" +#include "temporalfilter.h" namespace X265_NS { // x265 private namespace @@ -253,6 +254,12 @@ void x265_setup_primitives(x265_param *param) { setupCPrimitives(primitives); + /* MCTF primitives: scalar defaults, then x86 SIMD overrides on capable CPUs */ + setupMCTFPrimitives_scalar(mcstfPrim); +#if ENABLE_ASSEMBLY && X265_ARCH_X86 + setupMCTFPrimitives_x86(mcstfPrim, param->cpuid); +#endif + /* We do not want the encoder to use the un-optimized intra all-angles * C references. It is better to call the individual angle functions * instead. We must check for NULL before using this primitive */ diff --git a/source/common/temporalfilter.cpp b/source/common/temporalfilter.cpp index 32c8baba6..b2481fe7f 100644 --- a/source/common/temporalfilter.cpp +++ b/source/common/temporalfilter.cpp @@ -31,6 +31,76 @@ using namespace X265_NS; +namespace X265_NS { + + /* MCSTF scalar fallbacks - used when the runtime CPU lacks the required ISA.*/ + + static int motionErrorLumaFrac_c( + const pixel* origOrigin, intptr_t origStride, + const pixel* buffOrigin, intptr_t buffStride, + int x, int y, int dx, int dy, + int bs, int besterror, int bitDepth, int errorMode) + { + const int* xFilter = s_interpolationFilter[dx & 0xF]; + const int* yFilter = s_interpolationFilter[dy & 0xF]; + int tempArray[64 + 8][64]; + int error = 0; + + for (int y1 = 1; y1 < bs + 7; y1++) + { + const int yOffset = y + y1 + (dy >> 4) - 3; + const pixel* sourceRow = buffOrigin + yOffset * buffStride; + for (int x1 = 0; x1 < bs; x1++) + { + int iBase = x + x1 + (dx >> 4) - 3; + const pixel* rowStart = sourceRow + iBase; + int iSum = 0; + iSum += xFilter[1] * rowStart[1]; + iSum += xFilter[2] * rowStart[2]; + iSum += xFilter[3] * rowStart[3]; + iSum += xFilter[4] * rowStart[4]; + iSum += xFilter[5] * rowStart[5]; + iSum += xFilter[6] * rowStart[6]; + tempArray[y1][x1] = iSum; + } + } + + const int maxSampleValue = (1 << bitDepth) - 1; + for (int y1 = 0; y1 < bs; y1++) + { + const pixel* origRow = origOrigin + (y + y1) * origStride; + for (int x1 = 0; x1 < bs; x1++) + { + int iSum = 0; + iSum += yFilter[1] * tempArray[y1 + 1][x1]; + iSum += yFilter[2] * tempArray[y1 + 2][x1]; + iSum += yFilter[3] * tempArray[y1 + 3][x1]; + iSum += yFilter[4] * tempArray[y1 + 4][x1]; + iSum += yFilter[5] * tempArray[y1 + 5][x1]; + iSum += yFilter[6] * tempArray[y1 + 6][x1]; + iSum = (iSum + (1 << 11)) >> 12; + iSum = iSum < 0 ? 0 : (iSum > maxSampleValue ? maxSampleValue : iSum); + + int diff = iSum - origRow[x + x1]; + if (errorMode == 0) + error += abs(diff); + else + error += diff * diff; + } + if (error > besterror) + return error; + } + return error; + } + /* Global MCSTF primitives table */ + MCSTFPrimitives mcstfPrim; + + void setupMCTFPrimitives_scalar(MCSTFPrimitives& p) + { + p.motionErrorLumaFrac = motionErrorLumaFrac_c; + } +}/* namespace X265_NS */ + void OrigPicBuffer::addPicture(Frame* inFrame) { m_mcstfPicList.pushFrontMCSTF(*inFrame); @@ -244,56 +314,10 @@ int MotionEstimatorTLD::motionErrorLumaSAD(MotionEstimatorTLD& m_metld, } else { - const int *xFilter = s_interpolationFilter[dx & 0xF]; - const int *yFilter = s_interpolationFilter[dy & 0xF]; - int tempArray[64 + 8][64]; - - int iSum, iBase; - for (int y1 = 1; y1 < bs + 7; y1++) - { - const int yOffset = y + y1 + (dy >> 4) - 3; - const pixel *sourceRow = buffOrigin + (yOffset)*buffStride + 0; - for (int x1 = 0; x1 < bs; x1++) - { - iSum = 0; - iBase = x + x1 + (dx >> 4) - 3; - const pixel *rowStart = sourceRow + iBase; - - iSum += xFilter[1] * rowStart[1]; - iSum += xFilter[2] * rowStart[2]; - iSum += xFilter[3] * rowStart[3]; - iSum += xFilter[4] * rowStart[4]; - iSum += xFilter[5] * rowStart[5]; - iSum += xFilter[6] * rowStart[6]; - - tempArray[y1][x1] = iSum; - } - } - - const pixel maxSampleValue = (1 << m_bitDepth) - 1; - for (int y1 = 0; y1 < bs; y1++) - { - const pixel *origRow = origOrigin + (y + y1)*origStride + 0; - for (int x1 = 0; x1 < bs; x1++) - { - iSum = 0; - iSum += yFilter[1] * tempArray[y1 + 1][x1]; - iSum += yFilter[2] * tempArray[y1 + 2][x1]; - iSum += yFilter[3] * tempArray[y1 + 3][x1]; - iSum += yFilter[4] * tempArray[y1 + 4][x1]; - iSum += yFilter[5] * tempArray[y1 + 5][x1]; - iSum += yFilter[6] * tempArray[y1 + 6][x1]; - - iSum = (iSum + (1 << 11)) >> 12; - iSum = iSum < 0 ? 0 : (iSum > maxSampleValue ? maxSampleValue : iSum); - - error += abs(iSum - origRow[x + x1]); - } - if (error > besterror) - { - return error; - } - } + error = mcstfPrim.motionErrorLumaFrac( + origOrigin, origStride, buffOrigin, buffStride, + x, y, dx, dy, bs, besterror, m_bitDepth, 0 /*SAD*/); + if (error > besterror) return error; } return error; } @@ -350,56 +374,10 @@ int MotionEstimatorTLD::motionErrorLumaSSD(MotionEstimatorTLD& m_metld, } else { - const int *xFilter = s_interpolationFilter[dx & 0xF]; - const int *yFilter = s_interpolationFilter[dy & 0xF]; - int tempArray[64 + 8][64]; - - int iSum, iBase; - for (int y1 = 1; y1 < bs + 7; y1++) - { - const int yOffset = y + y1 + (dy >> 4) - 3; - const pixel *sourceRow = buffOrigin + (yOffset)*buffStride + 0; - for (int x1 = 0; x1 < bs; x1++) - { - iSum = 0; - iBase = x + x1 + (dx >> 4) - 3; - const pixel *rowStart = sourceRow + iBase; - - iSum += xFilter[1] * rowStart[1]; - iSum += xFilter[2] * rowStart[2]; - iSum += xFilter[3] * rowStart[3]; - iSum += xFilter[4] * rowStart[4]; - iSum += xFilter[5] * rowStart[5]; - iSum += xFilter[6] * rowStart[6]; - - tempArray[y1][x1] = iSum; - } - } - - const pixel maxSampleValue = (1 << m_bitDepth) - 1; - for (int y1 = 0; y1 < bs; y1++) - { - const pixel *origRow = origOrigin + (y + y1)*origStride + 0; - for (int x1 = 0; x1 < bs; x1++) - { - iSum = 0; - iSum += yFilter[1] * tempArray[y1 + 1][x1]; - iSum += yFilter[2] * tempArray[y1 + 2][x1]; - iSum += yFilter[3] * tempArray[y1 + 3][x1]; - iSum += yFilter[4] * tempArray[y1 + 4][x1]; - iSum += yFilter[5] * tempArray[y1 + 5][x1]; - iSum += yFilter[6] * tempArray[y1 + 6][x1]; - - iSum = (iSum + (1 << 11)) >> 12; - iSum = iSum < 0 ? 0 : (iSum > maxSampleValue ? maxSampleValue : iSum); - - error += (iSum - origRow[x + x1]) * (iSum - origRow[x + x1]); - } - if (error > besterror) - { - return error; - } - } + error = mcstfPrim.motionErrorLumaFrac( + origOrigin, origStride, buffOrigin, buffStride, + x, y, dx, dy, bs, besterror, m_bitDepth, 1 /*SAD*/); + if (error > besterror) return error; } return error; } diff --git a/source/common/temporalfilter.h b/source/common/temporalfilter.h index 87eb07b2b..aa34bf19d 100644 --- a/source/common/temporalfilter.h +++ b/source/common/temporalfilter.h @@ -61,6 +61,28 @@ const double s_refStrengths[3][4] = }; namespace X265_NS { + + /* MCSTF runtime SIMD dispatch + * Function-pointer table for MCSTF SIMD kernels. Defaults are scalar + * implementations defined in temporalfilter.cpp; setupMCTFPrimitives_x86 + * (in common/x86/temporalfilter_simd.cpp) overrides them with SSE4.1/AVX2 + * variants when the runtime CPU supports the required ISA.*/ + + struct MCSTFPrimitives + { + int (*motionErrorLumaFrac)(const pixel* origOrigin, intptr_t origStride, + const pixel* buffOrigin, intptr_t buffStride, + int x, int y, int dx, int dy, + int bs, int besterror, int bitDepth, int errorMode); + }; + + extern MCSTFPrimitives mcstfPrim; + + void setupMCTFPrimitives_scalar(MCSTFPrimitives& p); +#if X265_ARCH_X86 + void setupMCTFPrimitives_x86(MCSTFPrimitives& p, int cpuMask); +#endif + class OrigPicBuffer { public: diff --git a/source/common/x86/temporalfilter_simd.cpp b/source/common/x86/temporalfilter_simd.cpp new file mode 100644 index 000000000..279ee9cc1 --- /dev/null +++ b/source/common/x86/temporalfilter_simd.cpp @@ -0,0 +1,302 @@ +/***************************************************************************** +* Copyright (C) 2013-2021 MulticoreWare, Inc +* + * Authors: gunasrij + * +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA. +* +* This program is also available under a commercial proprietary license. +* For more information, contact us at license @ x265.com. +*****************************************************************************/ + +/* MCTF SIMD kernels - Provides AVX2 implementations of MCSTF primitives + * declared in temporalfilter.h (MCTFPrimitives). + * + * Entry point: setupMCTFPrimitives_x86(MCTFPrimitives&, int cpuMask) + * Called from primitives.cpp after setupMCTFPrimitives_scalar(). + * Overrides only the function pointers whose ISA requirement is met. + */ + +#if ENABLE_ASSEMBLY && X265_ARCH_X86 + +#include /* SSE2 */ +#include /* SSSE3 */ +#include /* SSE4.1 */ +#include /* AVX2 */ +#include + +#include "common.h" +#include "primitives.h" +#include "temporalfilter.h" +#include "mv.h" +#include "cpu.h" + + +#if defined(_MSC_VER) && !defined(__clang__) && !defined(__INTEL_COMPILER) +# define MCSTF_TARGET_AVX2 +#else +# define MCSTF_TARGET_AVX2 __attribute__((target("avx2"))) +#endif + +namespace X265_NS { + + /* Shared helpers */ + + static inline int hsum_epi32(__m128i v) + { + __m128i hi = _mm_srli_si128(v, 8); + v = _mm_add_epi32(v, hi); + hi = _mm_srli_si128(v, 4); + v = _mm_add_epi32(v, hi); + return _mm_cvtsi128_si32(v); + } + + MCSTF_TARGET_AVX2 + static inline int hsum_epi32_avx(__m256i v) + { + /* step 1: fold hi 128 into lo 128 */ + __m128i lo = _mm256_castsi256_si128(v); + __m128i hi = _mm256_extracti128_si256(v, 1); + __m128i sum = _mm_add_epi32(lo, hi); /* 4×int32 */ + + /* step 2: pair-sum within 128-bit lane */ + __m128i hi2 = _mm_srli_si128(sum, 8); + sum = _mm_add_epi32(sum, hi2); /* 2×int32 */ + __m128i hi3 = _mm_srli_si128(sum, 4); + sum = _mm_add_epi32(sum, hi3); /* 1×int32 */ + + return _mm_cvtsi128_si32(sum); + } + + MCSTF_TARGET_AVX2 + static inline __m256i load8px_epi32(const pixel* p) + { +#if X265_DEPTH > 8 + return _mm256_cvtepu16_epi32(_mm_loadu_si128((const __m128i*)p)); +#else + return _mm256_cvtepu8_epi32(_mm_loadl_epi64((const __m128i*)p)); +#endif + } + + MCSTF_TARGET_AVX2 + static inline __m256i load4px_epi32(const pixel* p) + { +#if X265_DEPTH > 8 + return _mm256_cvtepu16_epi32(_mm_loadl_epi64((const __m128i*)p)); +#else + return _mm256_cvtepu8_epi32(_mm_cvtsi32_si128(*(const int*)p)); +#endif + } + + MCSTF_TARGET_AVX2 + static inline void store8px(pixel* dst, __m256i v) + { +#if X265_DEPTH > 8 + /* _mm256_packus_epi32(v,v) → [lane0-3, lane0-3 | lane4-7, lane4-7]. + * permute4x64(0x08) moves qwords 0 and 2 into the lower 128 bits. */ + __m256i packed = _mm256_packus_epi32(v, v); + __m128i out = _mm256_castsi256_si128( + _mm256_permute4x64_epi64(packed, 0x08)); + _mm_storeu_si128((__m128i*)dst, out); +#else + __m256i as16 = _mm256_packs_epi32(v, v); + __m128i lo = _mm256_castsi256_si128(as16); + __m128i hi = _mm256_extracti128_si256(as16, 1); + __m128i merged = _mm_unpacklo_epi64(lo, hi); + __m128i out8 = _mm_packus_epi16(merged, merged); + _mm_storel_epi64((__m128i*)dst, out8); +#endif + } + + MCSTF_TARGET_AVX2 + static inline void store4px(pixel* dst, __m256i v) + { + __m128i lo = _mm256_castsi256_si128(v); /* only lower 4 lanes valid */ +#if X265_DEPTH > 8 + __m128i out = _mm_packus_epi32(lo, _mm_setzero_si128()); + _mm_storel_epi64((__m128i*)dst, out); +#else + __m128i as16 = _mm_packs_epi32(lo, _mm_setzero_si128()); + __m128i out8 = _mm_packus_epi16(as16, _mm_setzero_si128()); + *(int*)dst = _mm_cvtsi128_si32(out8); +#endif + } + + MCSTF_TARGET_AVX2 + static int motionErrorLumaFrac_SIMD( + const pixel* origOrigin, intptr_t origStride, + const pixel* buffOrigin, intptr_t buffStride, + int x, int y, int dx, int dy, + int bs, int besterror, int bitDepth, int errorMode) + { + const int* xFilter = s_interpolationFilter[dx & 0xF]; + const int* yFilter = s_interpolationFilter[dy & 0xF]; + + int tempArray[64 + 8][64]; + const int int_dx = dx >> 4; + const int int_dy = dy >> 4; + + const __m128i xf12_128 = _mm_unpacklo_epi16( + _mm_set1_epi16((int16_t)xFilter[1]), + _mm_set1_epi16((int16_t)xFilter[2])); + const __m128i xf34_128 = _mm_unpacklo_epi16( + _mm_set1_epi16((int16_t)xFilter[3]), + _mm_set1_epi16((int16_t)xFilter[4])); + const __m128i xf56_128 = _mm_unpacklo_epi16( + _mm_set1_epi16((int16_t)xFilter[5]), + _mm_set1_epi16((int16_t)xFilter[6])); + + const __m256i xf12 = _mm256_set_m128i(xf12_128, xf12_128); + const __m256i xf34 = _mm256_set_m128i(xf34_128, xf34_128); + const __m256i xf56 = _mm256_set_m128i(xf56_128, xf56_128); + + for (int y1 = 1; y1 < bs + 7; y1++) + { + const pixel* rowStart0 = buffOrigin + + (y + y1 + int_dy - 3) * buffStride + + (x + int_dx - 3); + + for (int x1 = 0; x1 < bs; x1 += 8) + { + const pixel* rowStart = rowStart0 + x1; + + +#if X265_DEPTH > 8 + /* uint16 pixels: load 8× uint16 directly into 128-bit reg. + * _mm256_set_m128i places lo_half in [127:0] and hi_half in + * [255:128]. We build the 256-bit interleaved pairs: + * pairs_ab[255:0] = { unpackhi(s_a, s_b) | unpacklo(s_a, s_b) } + * so madd lane i gives s_a[i]*f_a + s_b[i]*f_b. */ + __m128i s1 = _mm_loadu_si128((const __m128i*) & rowStart[1]); + __m128i s2 = _mm_loadu_si128((const __m128i*) & rowStart[2]); + __m128i s3 = _mm_loadu_si128((const __m128i*) & rowStart[3]); + __m128i s4 = _mm_loadu_si128((const __m128i*) & rowStart[4]); + __m128i s5 = _mm_loadu_si128((const __m128i*) & rowStart[5]); + __m128i s6 = _mm_loadu_si128((const __m128i*) & rowStart[6]); +#else + /* uint8 pixels: load 8 bytes, zero-extend to 8×int16. */ + __m128i s1 = _mm_cvtepu8_epi16(_mm_loadl_epi64((const __m128i*) & rowStart[1])); + __m128i s2 = _mm_cvtepu8_epi16(_mm_loadl_epi64((const __m128i*) & rowStart[2])); + __m128i s3 = _mm_cvtepu8_epi16(_mm_loadl_epi64((const __m128i*) & rowStart[3])); + __m128i s4 = _mm_cvtepu8_epi16(_mm_loadl_epi64((const __m128i*) & rowStart[4])); + __m128i s5 = _mm_cvtepu8_epi16(_mm_loadl_epi64((const __m128i*) & rowStart[5])); + __m128i s6 = _mm_cvtepu8_epi16(_mm_loadl_epi64((const __m128i*) & rowStart[6])); +#endif + + __m256i pairs12 = _mm256_set_m128i( + _mm_unpackhi_epi16(s1, s2), + _mm_unpacklo_epi16(s1, s2)); + __m256i pairs34 = _mm256_set_m128i( + _mm_unpackhi_epi16(s3, s4), + _mm_unpacklo_epi16(s3, s4)); + __m256i pairs56 = _mm256_set_m128i( + _mm_unpackhi_epi16(s5, s6), + _mm_unpacklo_epi16(s5, s6)); + + __m256i h_out = _mm256_add_epi32( + _mm256_add_epi32( + _mm256_madd_epi16(pairs12, xf12), + _mm256_madd_epi16(pairs34, xf34)), + _mm256_madd_epi16(pairs56, xf56)); + + /* Store 8 int32 values — matches scalar tempArray[y1][x1..x1+7]. */ + _mm256_storeu_si256((__m256i*) & tempArray[y1][x1], h_out); + } + } + + const __m256i yt1 = _mm256_set1_epi32(yFilter[1]); + const __m256i yt2 = _mm256_set1_epi32(yFilter[2]); + const __m256i yt3 = _mm256_set1_epi32(yFilter[3]); + const __m256i yt4 = _mm256_set1_epi32(yFilter[4]); + const __m256i yt5 = _mm256_set1_epi32(yFilter[5]); + const __m256i yt6 = _mm256_set1_epi32(yFilter[6]); + const __m256i vmax = _mm256_set1_epi32((1 << bitDepth) - 1); + const __m256i vmin = _mm256_setzero_si256(); + const __m256i round_v = _mm256_set1_epi32(1 << 11); + + __m256i xerror = _mm256_setzero_si256(); + + for (int outY = 0; outY < bs; outY++) + { + const pixel* origRowBase = origOrigin + (y + outY) * origStride + x; + + for (int x1 = 0; x1 < bs; x1 += 8) + { + /* Load 6 rows of temp data for this column group. */ + __m256i t1 = _mm256_loadu_si256((const __m256i*) & tempArray[outY + 1][x1]); + __m256i t2 = _mm256_loadu_si256((const __m256i*) & tempArray[outY + 2][x1]); + __m256i t3 = _mm256_loadu_si256((const __m256i*) & tempArray[outY + 3][x1]); + __m256i t4 = _mm256_loadu_si256((const __m256i*) & tempArray[outY + 4][x1]); + __m256i t5 = _mm256_loadu_si256((const __m256i*) & tempArray[outY + 5][x1]); + __m256i t6 = _mm256_loadu_si256((const __m256i*) & tempArray[outY + 6][x1]); + + /* Vertical filter — same addition order as scalar. */ + __m256i v = _mm256_add_epi32( + _mm256_add_epi32( + _mm256_add_epi32(_mm256_mullo_epi32(t1, yt1), + _mm256_mullo_epi32(t2, yt2)), + _mm256_add_epi32(_mm256_mullo_epi32(t3, yt3), + _mm256_mullo_epi32(t4, yt4))), + _mm256_add_epi32(_mm256_mullo_epi32(t5, yt5), + _mm256_mullo_epi32(t6, yt6))); + + /* Shift + clamp - identical to scalar: + * iSum = (iSum + (1<<11)) >> 12 + * iSum = clamp(iSum, 0, maxSampleValue) */ + v = _mm256_srai_epi32(_mm256_add_epi32(v, round_v), 12); + v = _mm256_min_epi32(_mm256_max_epi32(v, vmin), vmax); + + /* Load 8 original pixels as int32. */ +#if X265_DEPTH > 8 + __m256i orig = _mm256_cvtepu16_epi32( + _mm_loadu_si128((const __m128i*) & origRowBase[x1])); +#else + __m128i xorig = _mm_loadl_epi64((const __m128i*) & origRowBase[x1]); + __m256i orig = _mm256_set_m128i( + _mm_cvtepu8_epi32(_mm_srli_si128(xorig, 4)), + _mm_cvtepu8_epi32(xorig)); +#endif + + /* diff = filtered_pixel - orig_pixel */ + __m256i diff = _mm256_sub_epi32(v, orig); + + __m256i row_err; + if (errorMode == 0) /* SAD */ + row_err = _mm256_abs_epi32(diff); + else /* SSD */ + row_err = _mm256_mullo_epi32(diff, diff); + + xerror = _mm256_add_epi32(xerror, row_err); + } + + int error = hsum_epi32_avx(xerror); + if (error > besterror) + return error; + } + + return hsum_epi32_avx(xerror); + } + + /* Dispatch - overrides scalar defaults with SIMD variants at runtime */ + void setupMCTFPrimitives_x86(MCSTFPrimitives & p, int cpuMask) + { + if (cpuMask & X265_CPU_AVX2) + { + p.motionErrorLumaFrac = motionErrorLumaFrac_SIMD; + } + } +} /* namespace X265_NS */ + +#endif /* ENABLE_ASSEMBLY && X265_ARCH_X86 */ \ No newline at end of file From 7990512ade5dc5fe25b22a5062fbee58460dc647 Mon Sep 17 00:00:00 2001 From: gunasrij Date: Sun, 28 Jun 2026 22:24:06 +0530 Subject: [PATCH 06/23] mcstf: Add AVX2 implementation for applyMotion --- source/common/temporalfilter.cpp | 166 ++++++++++------- source/common/temporalfilter.h | 7 + source/common/x86/temporalfilter_simd.cpp | 211 ++++++++++++++++++++-- 3 files changed, 301 insertions(+), 83 deletions(-) diff --git a/source/common/temporalfilter.cpp b/source/common/temporalfilter.cpp index b2481fe7f..b559f0985 100644 --- a/source/common/temporalfilter.cpp +++ b/source/common/temporalfilter.cpp @@ -92,12 +92,102 @@ namespace X265_NS { } return error; } + static void applyMotion_c( + const pixel* pSrcImage, int srcStride, + pixel* pDstImage, int dstStride, + int width, int height, + int blockSizeX, int blockSizeY, + uint32_t mvsStride, const MV* mvs, + int csx, int csy, + int blockRow, int rowSize, int vShift) + { + static const int numFilterTaps = 7; + static const int centreTapOffset = 3; + const int maxValue = (1 << X265_DEPTH) - 1; + + const int blkRowStart = (blockRow * rowSize) >> vShift; + const int blkRowEnd = X265_MIN((blockRow * rowSize + rowSize) >> vShift, height); + const int rowStart = (!rowSize) ? 0 : blkRowStart; + const int rowEnd = (!rowSize) ? height : blkRowEnd; + int blockNumY = (!rowSize) ? 0 : blkRowStart / blockSizeY; + + for (int y = rowStart; + y + blockSizeY <= rowEnd; + y += blockSizeY, blockNumY++) + { + for (int x = 0, blockNumX = 0; + x + blockSizeX <= width; + x += blockSizeX, blockNumX++) + { + const int mvIdx = blockNumY * (int)mvsStride + blockNumX; + const MV& mv = mvs[mvIdx]; + + const int dx = mv.x >> csx; + const int dy = mv.y >> csy; + const int xInt = mv.x >> (4 + csx); + const int yInt = mv.y >> (4 + csy); + + const int* xFilter = s_interpolationFilter[dx & 0xf]; + const int* yFilter = s_interpolationFilter[dy & 0xf]; + + int tempArray[8 + numFilterTaps][8]; + + for (int by = 1; by < blockSizeY + numFilterTaps; by++) + { + const int yOffset = y + by + yInt - centreTapOffset; + const pixel* sourceRow = pSrcImage + yOffset * srcStride; + + for (int bx = 0; bx < blockSizeX; bx++) + { + int iBase = x + bx + xInt - centreTapOffset; + const pixel* rowStart = sourceRow + iBase; + + int iSum = 0; + iSum += xFilter[1] * rowStart[1]; + iSum += xFilter[2] * rowStart[2]; + iSum += xFilter[3] * rowStart[3]; + iSum += xFilter[4] * rowStart[4]; + iSum += xFilter[5] * rowStart[5]; + iSum += xFilter[6] * rowStart[6]; + + tempArray[by][bx] = iSum; + } + } + + pixel* pDstRow = pDstImage + y * dstStride; + + for (int by = 0; by < blockSizeY; by++, pDstRow += dstStride) + { + pixel* pDstPel = pDstRow + x; + + for (int bx = 0; bx < blockSizeX; bx++, pDstPel++) + { + int iSum = 0; + + iSum += yFilter[1] * tempArray[by + 1][bx]; + iSum += yFilter[2] * tempArray[by + 2][bx]; + iSum += yFilter[3] * tempArray[by + 3][bx]; + iSum += yFilter[4] * tempArray[by + 4][bx]; + iSum += yFilter[5] * tempArray[by + 5][bx]; + iSum += yFilter[6] * tempArray[by + 6][bx]; + + iSum = (iSum + (1 << 11)) >> 12; + iSum = iSum < 0 ? 0 : (iSum > maxValue ? maxValue : iSum); + + *pDstPel = (pixel)iSum; + } + } + } + } + } + /* Global MCSTF primitives table */ MCSTFPrimitives mcstfPrim; void setupMCTFPrimitives_scalar(MCSTFPrimitives& p) { p.motionErrorLumaFrac = motionErrorLumaFrac_c; + p.applyMotion = applyMotion_c; } }/* namespace X265_NS */ @@ -392,8 +482,8 @@ void TemporalFilter::applyMotion(MV *mvs, uint32_t mvsStride, PicYuv *input, Pic { const pixel maxValue = (1 << X265_DEPTH) - 1; - const pixel *pSrcImage = input->m_picOrg[c]; - pixel *pDstImage = output->m_picOrg[c]; + const pixel* pSrcImage = input->m_picOrg[c]; + pixel* pDstImage = output->m_picOrg[c]; if (!c) { @@ -412,73 +502,15 @@ void TemporalFilter::applyMotion(MV *mvs, uint32_t mvsStride, PicYuv *input, Pic const int height = input->m_picHeight >> csy; const int width = input->m_picWidth >> csx; - const int vShift = (!c) ? 0 : csy; - const int blkRowStart = (!rowSize) ? 0 : (blockRow * rowSize) >> vShift; - const int blkRowEnd = (!rowSize) ? height : X265_MIN((blockRow * rowSize + rowSize) >> vShift, height); - - int blockNumY = (!rowSize) ? 0 : blkRowStart / blockSizeY; - - for (int y = blkRowStart; y + blockSizeY <= blkRowEnd; y += blockSizeY, blockNumY++) - { - for (int x = 0, blockNumX = 0; x + blockSizeX <= width; x += blockSizeX, blockNumX++) - { - int mvIdx = blockNumY * mvsStride + blockNumX; - const MV &mv = mvs[mvIdx]; - const int dx = mv.x >> csx; - const int dy = mv.y >> csy; - const int xInt = mv.x >> (4 + csx); - const int yInt = mv.y >> (4 + csy); - - const int *xFilter = s_interpolationFilter[dx & 0xf]; - const int *yFilter = s_interpolationFilter[dy & 0xf]; // will add 6 bit. - const int numFilterTaps = 7; - const int centreTapOffset = 3; - - int tempArray[lumaBlockSize + numFilterTaps][lumaBlockSize]; - - for (int by = 1; by < blockSizeY + numFilterTaps; by++) - { - const int yOffset = y + by + yInt - centreTapOffset; - const pixel *sourceRow = pSrcImage + yOffset * srcStride; - for (int bx = 0; bx < blockSizeX; bx++) - { - int iBase = x + bx + xInt - centreTapOffset; - const pixel *rowStart = sourceRow + iBase; - - int iSum = 0; - iSum += xFilter[1] * rowStart[1]; - iSum += xFilter[2] * rowStart[2]; - iSum += xFilter[3] * rowStart[3]; - iSum += xFilter[4] * rowStart[4]; - iSum += xFilter[5] * rowStart[5]; - iSum += xFilter[6] * rowStart[6]; - - tempArray[by][bx] = iSum; - } - } - - pixel *pDstRow = pDstImage + y * dstStride; - for (int by = 0; by < blockSizeY; by++, pDstRow += dstStride) - { - pixel *pDstPel = pDstRow + x; - for (int bx = 0; bx < blockSizeX; bx++, pDstPel++) - { - int iSum = 0; + const int vShift = (!c) ? 0 : csy; + const int blkRowStart = (blockRow * rowSize) >> vShift; + const int blkRowEnd = X265_MIN((blockRow * rowSize + rowSize) >> vShift, height); - iSum += yFilter[1] * tempArray[by + 1][bx]; - iSum += yFilter[2] * tempArray[by + 2][bx]; - iSum += yFilter[3] * tempArray[by + 3][bx]; - iSum += yFilter[4] * tempArray[by + 4][bx]; - iSum += yFilter[5] * tempArray[by + 5][bx]; - iSum += yFilter[6] * tempArray[by + 6][bx]; + const int rowStart = (!rowSize) ? 0 : blkRowStart; + const int rowEnd = (!rowSize) ? height : blkRowEnd; + int blockNumY = (!rowSize) ? 0 : blkRowStart / blockSizeY; - iSum = (iSum + (1 << 11)) >> 12; - iSum = iSum < 0 ? 0 : (iSum > maxValue ? maxValue : iSum); - *pDstPel = (pixel)iSum; - } - } - } - } + mcstfPrim.applyMotion(pSrcImage, srcStride, pDstImage, dstStride, width, height, blockSizeX, blockSizeY, mvsStride, mvs, csx, csy, blockRow, rowSize, vShift); } } diff --git a/source/common/temporalfilter.h b/source/common/temporalfilter.h index aa34bf19d..dcb21c9e3 100644 --- a/source/common/temporalfilter.h +++ b/source/common/temporalfilter.h @@ -74,6 +74,13 @@ namespace X265_NS { const pixel* buffOrigin, intptr_t buffStride, int x, int y, int dx, int dy, int bs, int besterror, int bitDepth, int errorMode); + void (*applyMotion)(const pixel* pSrcImage, int srcStride, + pixel* pDstImage, int dstStride, + int width, int height, + int blockSizeX, int blockSizeY, + uint32_t mvsStride, const MV* mvs, + int csx, int csy, + int blockRow, int rowSize, int vShift); }; extern MCSTFPrimitives mcstfPrim; diff --git a/source/common/x86/temporalfilter_simd.cpp b/source/common/x86/temporalfilter_simd.cpp index 279ee9cc1..438a541a9 100644 --- a/source/common/x86/temporalfilter_simd.cpp +++ b/source/common/x86/temporalfilter_simd.cpp @@ -66,16 +66,14 @@ namespace X265_NS { MCSTF_TARGET_AVX2 static inline int hsum_epi32_avx(__m256i v) { - /* step 1: fold hi 128 into lo 128 */ __m128i lo = _mm256_castsi256_si128(v); __m128i hi = _mm256_extracti128_si256(v, 1); - __m128i sum = _mm_add_epi32(lo, hi); /* 4×int32 */ + __m128i sum = _mm_add_epi32(lo, hi); - /* step 2: pair-sum within 128-bit lane */ __m128i hi2 = _mm_srli_si128(sum, 8); - sum = _mm_add_epi32(sum, hi2); /* 2×int32 */ + sum = _mm_add_epi32(sum, hi2); __m128i hi3 = _mm_srli_si128(sum, 4); - sum = _mm_add_epi32(sum, hi3); /* 1×int32 */ + sum = _mm_add_epi32(sum, hi3); return _mm_cvtsi128_si32(sum); } @@ -135,7 +133,7 @@ namespace X265_NS { } MCSTF_TARGET_AVX2 - static int motionErrorLumaFrac_SIMD( + static int motionErrorLumaFrac_AVX2( const pixel* origOrigin, intptr_t origStride, const pixel* buffOrigin, intptr_t buffStride, int x, int y, int dx, int dy, @@ -186,7 +184,6 @@ namespace X265_NS { __m128i s5 = _mm_loadu_si128((const __m128i*) & rowStart[5]); __m128i s6 = _mm_loadu_si128((const __m128i*) & rowStart[6]); #else - /* uint8 pixels: load 8 bytes, zero-extend to 8×int16. */ __m128i s1 = _mm_cvtepu8_epi16(_mm_loadl_epi64((const __m128i*) & rowStart[1])); __m128i s2 = _mm_cvtepu8_epi16(_mm_loadl_epi64((const __m128i*) & rowStart[2])); __m128i s3 = _mm_cvtepu8_epi16(_mm_loadl_epi64((const __m128i*) & rowStart[3])); @@ -211,7 +208,6 @@ namespace X265_NS { _mm256_madd_epi16(pairs34, xf34)), _mm256_madd_epi16(pairs56, xf56)); - /* Store 8 int32 values — matches scalar tempArray[y1][x1..x1+7]. */ _mm256_storeu_si256((__m256i*) & tempArray[y1][x1], h_out); } } @@ -234,7 +230,6 @@ namespace X265_NS { for (int x1 = 0; x1 < bs; x1 += 8) { - /* Load 6 rows of temp data for this column group. */ __m256i t1 = _mm256_loadu_si256((const __m256i*) & tempArray[outY + 1][x1]); __m256i t2 = _mm256_loadu_si256((const __m256i*) & tempArray[outY + 2][x1]); __m256i t3 = _mm256_loadu_si256((const __m256i*) & tempArray[outY + 3][x1]); @@ -242,7 +237,6 @@ namespace X265_NS { __m256i t5 = _mm256_loadu_si256((const __m256i*) & tempArray[outY + 5][x1]); __m256i t6 = _mm256_loadu_si256((const __m256i*) & tempArray[outY + 6][x1]); - /* Vertical filter — same addition order as scalar. */ __m256i v = _mm256_add_epi32( _mm256_add_epi32( _mm256_add_epi32(_mm256_mullo_epi32(t1, yt1), @@ -252,13 +246,9 @@ namespace X265_NS { _mm256_add_epi32(_mm256_mullo_epi32(t5, yt5), _mm256_mullo_epi32(t6, yt6))); - /* Shift + clamp - identical to scalar: - * iSum = (iSum + (1<<11)) >> 12 - * iSum = clamp(iSum, 0, maxSampleValue) */ v = _mm256_srai_epi32(_mm256_add_epi32(v, round_v), 12); v = _mm256_min_epi32(_mm256_max_epi32(v, vmin), vmax); - /* Load 8 original pixels as int32. */ #if X265_DEPTH > 8 __m256i orig = _mm256_cvtepu16_epi32( _mm_loadu_si128((const __m128i*) & origRowBase[x1])); @@ -269,7 +259,6 @@ namespace X265_NS { _mm_cvtepu8_epi32(xorig)); #endif - /* diff = filtered_pixel - orig_pixel */ __m256i diff = _mm256_sub_epi32(v, orig); __m256i row_err; @@ -289,12 +278,202 @@ namespace X265_NS { return hsum_epi32_avx(xerror); } + /* Per-block separable 6-tap filter worker */ + MCSTF_TARGET_AVX2 + static void applyMotionBlock_AVX2( + const pixel* pSrcImage, int srcStride, + pixel* pDstImage, int dstStride, + int x, int y, + int blockSizeX, int blockSizeY, + int xInt, int yInt, + const int* xFilter, + const int* yFilter) + { + int tempArray[15][8]; + + const __m256i xf1 = _mm256_set1_epi32(xFilter[1]); + const __m256i xf2 = _mm256_set1_epi32(xFilter[2]); + const __m256i xf3 = _mm256_set1_epi32(xFilter[3]); + const __m256i xf4 = _mm256_set1_epi32(xFilter[4]); + const __m256i xf5 = _mm256_set1_epi32(xFilter[5]); + const __m256i xf6 = _mm256_set1_epi32(xFilter[6]); + + const __m256i yf1 = _mm256_set1_epi32(yFilter[1]); + const __m256i yf2 = _mm256_set1_epi32(yFilter[2]); + const __m256i yf3 = _mm256_set1_epi32(yFilter[3]); + const __m256i yf4 = _mm256_set1_epi32(yFilter[4]); + const __m256i yf5 = _mm256_set1_epi32(yFilter[5]); + const __m256i yf6 = _mm256_set1_epi32(yFilter[6]); + + const __m256i vround = _mm256_set1_epi32(1 << 11); + const __m256i vmin = _mm256_setzero_si256(); + const __m256i vmax = _mm256_set1_epi32((1 << X265_DEPTH) - 1); + + const int hColBase = x + xInt - 3; + const int hRowEnd = blockSizeY + 6; + + /* H-PASS */ + if (blockSizeX == 8) + { + for (int by = 1; by <= hRowEnd; by++) + { + const pixel* srcRow = pSrcImage + (y + by + yInt - 3) * srcStride; + + __m256i s1 = load8px_epi32(srcRow + hColBase + 1); + __m256i s2 = load8px_epi32(srcRow + hColBase + 2); + __m256i s3 = load8px_epi32(srcRow + hColBase + 3); + __m256i s4 = load8px_epi32(srcRow + hColBase + 4); + __m256i s5 = load8px_epi32(srcRow + hColBase + 5); + __m256i s6 = load8px_epi32(srcRow + hColBase + 6); + + __m256i acc = _mm256_mullo_epi32(s1, xf1); + acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(s2, xf2)); + acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(s3, xf3)); + acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(s4, xf4)); + acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(s5, xf5)); + acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(s6, xf6)); + + _mm256_storeu_si256((__m256i*) & tempArray[by][0], acc); + } + } + else + { + for (int by = 1; by <= hRowEnd; by++) + { + const pixel* srcRow = pSrcImage + (y + by + yInt - 3) * srcStride; + + __m256i s1 = load4px_epi32(srcRow + hColBase + 1); + __m256i s2 = load4px_epi32(srcRow + hColBase + 2); + __m256i s3 = load4px_epi32(srcRow + hColBase + 3); + __m256i s4 = load4px_epi32(srcRow + hColBase + 4); + __m256i s5 = load4px_epi32(srcRow + hColBase + 5); + __m256i s6 = load4px_epi32(srcRow + hColBase + 6); + + __m256i acc = _mm256_mullo_epi32(s1, xf1); + acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(s2, xf2)); + acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(s3, xf3)); + acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(s4, xf4)); + acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(s5, xf5)); + acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(s6, xf6)); + + _mm_storeu_si128((__m128i*) & tempArray[by][0], + _mm256_castsi256_si128(acc)); + } + } + + /* V-PASS */ + if (blockSizeX == 8) + { + for (int by = 0; by < blockSizeY; by++) + { + pixel* pDstPel = pDstImage + (y + by) * dstStride + x; + + __m256i t1 = _mm256_loadu_si256((const __m256i*) & tempArray[by + 1][0]); + __m256i t2 = _mm256_loadu_si256((const __m256i*) & tempArray[by + 2][0]); + __m256i t3 = _mm256_loadu_si256((const __m256i*) & tempArray[by + 3][0]); + __m256i t4 = _mm256_loadu_si256((const __m256i*) & tempArray[by + 4][0]); + __m256i t5 = _mm256_loadu_si256((const __m256i*) & tempArray[by + 5][0]); + __m256i t6 = _mm256_loadu_si256((const __m256i*) & tempArray[by + 6][0]); + + __m256i acc = _mm256_mullo_epi32(t1, yf1); + acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(t2, yf2)); + acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(t3, yf3)); + acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(t4, yf4)); + acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(t5, yf5)); + acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(t6, yf6)); + + acc = _mm256_add_epi32(acc, vround); + acc = _mm256_srai_epi32(acc, 12); + + acc = _mm256_max_epi32(acc, vmin); + acc = _mm256_min_epi32(acc, vmax); + + store8px(pDstPel, acc); + } + } + else + { + for (int by = 0; by < blockSizeY; by++) + { + pixel* pDstPel = pDstImage + (y + by) * dstStride + x; + + __m256i t1 = _mm256_castsi128_si256(_mm_loadu_si128((const __m128i*) & tempArray[by + 1][0])); + __m256i t2 = _mm256_castsi128_si256(_mm_loadu_si128((const __m128i*) & tempArray[by + 2][0])); + __m256i t3 = _mm256_castsi128_si256(_mm_loadu_si128((const __m128i*) & tempArray[by + 3][0])); + __m256i t4 = _mm256_castsi128_si256(_mm_loadu_si128((const __m128i*) & tempArray[by + 4][0])); + __m256i t5 = _mm256_castsi128_si256(_mm_loadu_si128((const __m128i*) & tempArray[by + 5][0])); + __m256i t6 = _mm256_castsi128_si256(_mm_loadu_si128((const __m128i*) & tempArray[by + 6][0])); + + __m256i acc = _mm256_mullo_epi32(t1, yf1); + acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(t2, yf2)); + acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(t3, yf3)); + acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(t4, yf4)); + acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(t5, yf5)); + acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(t6, yf6)); + + acc = _mm256_add_epi32(acc, vround); + acc = _mm256_srai_epi32(acc, 12); + acc = _mm256_max_epi32(acc, vmin); + acc = _mm256_min_epi32(acc, vmax); + + store4px(pDstPel, acc); + } + } + } + + MCSTF_TARGET_AVX2 + static void applyMotion_AVX2( + const pixel* pSrcImage, int srcStride, + pixel* pDstImage, int dstStride, + int width, int height, + int blockSizeX, int blockSizeY, + uint32_t mvsStride, const MV* mvs, + int csx, int csy, + int blockRow, int rowSize, int vShift) + { + const int blkRowStart = (blockRow * rowSize) >> vShift; + const int blkRowEnd = X265_MIN((blockRow * rowSize + rowSize) >> vShift, height); + const int rowStart = (!rowSize) ? 0 : blkRowStart; + const int rowEnd = (!rowSize) ? height : blkRowEnd; + int blockNumY = (!rowSize) ? 0 : blkRowStart / blockSizeY; + + for (int y = rowStart; + y + blockSizeY <= rowEnd; + y += blockSizeY, blockNumY++) + { + for (int x = 0, blockNumX = 0; + x + blockSizeX <= width; + x += blockSizeX, blockNumX++) + { + const int mvIdx = blockNumY * (int)mvsStride + blockNumX; + const MV& mv = mvs[mvIdx]; + + const int dx = mv.x >> csx; + const int dy = mv.y >> csy; + const int xInt = mv.x >> (4 + csx); + const int yInt = mv.y >> (4 + csy); + + const int* xFilter = s_interpolationFilter[dx & 0xf]; + const int* yFilter = s_interpolationFilter[dy & 0xf]; + + applyMotionBlock_AVX2( + pSrcImage, srcStride, + pDstImage, dstStride, + x, y, + blockSizeX, blockSizeY, + xInt, yInt, + xFilter, yFilter); + } + } + } + /* Dispatch - overrides scalar defaults with SIMD variants at runtime */ void setupMCTFPrimitives_x86(MCSTFPrimitives & p, int cpuMask) { if (cpuMask & X265_CPU_AVX2) { - p.motionErrorLumaFrac = motionErrorLumaFrac_SIMD; + p.motionErrorLumaFrac = motionErrorLumaFrac_AVX2; + p.applyMotion = applyMotion_AVX2; } } } /* namespace X265_NS */ From 69abeb16f300a50e4f24fd5164148a93f451ffcd Mon Sep 17 00:00:00 2001 From: gunasrij Date: Mon, 25 May 2026 18:50:01 +0530 Subject: [PATCH 07/23] mcstf: disable MCSTF when bFrameAdaptive=0 --- source/encoder/encoder.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/source/encoder/encoder.cpp b/source/encoder/encoder.cpp index 2c8f8b73b..adfff307c 100644 --- a/source/encoder/encoder.cpp +++ b/source/encoder/encoder.cpp @@ -4255,10 +4255,16 @@ void Encoder::configure(x265_param *p) p->limitReferences = 0; } - if ((m_param->bEnableTemporalFilter) && (p->bframes < 5)){ + if ((p->bEnableTemporalFilter) && (p->bFrameAdaptive == 0)) { + x265_log(p, X265_LOG_WARNING, "MCSTF is unsupported when b-adapt = 0. Disabling MCSTF.\n"); + p->bEnableTemporalFilter = 0; + } + + if ((p->bEnableTemporalFilter) && (p->bframes < 5)){ x265_log(p, X265_LOG_WARNING, "Setting the number of B-frames to 5, as MCSTF filter is enabled.\n"); p->bframes = 5; } + if ((p->bEnableTemporalSubLayers > 2) && !p->bframes) { x265_log(p, X265_LOG_WARNING, "B frames not enabled, temporal sublayer disabled\n"); From adeae33b763a5769e814d6e570df50d2d6ecf98e Mon Sep 17 00:00:00 2001 From: gunasrij Date: Mon, 29 Jun 2026 00:11:45 +0530 Subject: [PATCH 08/23] mcstf: add AVX2 implementations --- source/common/temporalfilter.cpp | 25 ++----- source/common/temporalfilter.h | 3 + source/common/x86/temporalfilter_simd.cpp | 79 +++++++++++++++++++++++ 3 files changed, 86 insertions(+), 21 deletions(-) diff --git a/source/common/temporalfilter.cpp b/source/common/temporalfilter.cpp index b559f0985..c933002d3 100644 --- a/source/common/temporalfilter.cpp +++ b/source/common/temporalfilter.cpp @@ -1005,27 +1005,10 @@ void MotionEstimatorTLD::motionEstimationLumaDoubleRes(MotionEstimatorTLD& m_met } } - // calculate average - double avg = 0.0; - for (int x1 = 0; x1 < blockSize; x1++) - { - for (int y1 = 0; y1 < blockSize; y1++) - { - avg = avg + *(orig->m_picOrg[0] + (blockX + x1 + orig->m_stride * (blockY + y1))); - } - } - avg = avg / (blockSize * blockSize); - - // calculate variance - double variance = 0; - for (int x1 = 0; x1 < blockSize; x1++) - { - for (int y1 = 0; y1 < blockSize; y1++) - { - int pix = *(orig->m_picOrg[0] + (blockX + x1 + orig->m_stride * (blockY + y1))); - variance = variance + (pix - avg) * (pix - avg); - } - } + double avg, variance; + mcstfPrim.lumaBlockAvgVariance( + orig->m_picOrg[0], orig->m_stride, + blockX, blockY, blockSize, &avg, &variance); leastError = (int)(20 * ((leastError + 5.0) / (variance + 5.0)) + (leastError / (blockSize * blockSize)) / 50); diff --git a/source/common/temporalfilter.h b/source/common/temporalfilter.h index dcb21c9e3..e1a62c859 100644 --- a/source/common/temporalfilter.h +++ b/source/common/temporalfilter.h @@ -81,6 +81,9 @@ namespace X265_NS { uint32_t mvsStride, const MV* mvs, int csx, int csy, int blockRow, int rowSize, int vShift); + void (*lumaBlockAvgVariance)(const pixel* origin, intptr_t stride, + int blockX, int blockY, int blockSize, + double* avgOut, double* varianceOut); }; extern MCSTFPrimitives mcstfPrim; diff --git a/source/common/x86/temporalfilter_simd.cpp b/source/common/x86/temporalfilter_simd.cpp index 438a541a9..606e3c3f1 100644 --- a/source/common/x86/temporalfilter_simd.cpp +++ b/source/common/x86/temporalfilter_simd.cpp @@ -467,6 +467,84 @@ namespace X265_NS { } } + MCSTF_TARGET_AVX2 + static void lumaBlockAvgVariance_AVX2(const pixel* origin, intptr_t stride, int blockX,int blockY,int blockSize,double* avgOut, double* varianceOut) + { + const pixel* base = origin + blockY * stride + blockX; + + __m256i sumAcc = _mm256_setzero_si256(); + + for (int y1 = 0; y1 < blockSize; y1++) + { + const pixel* row = base + y1 * stride; + + for (int x1 = 0; x1 < blockSize; x1 += 8) + { +#if X265_DEPTH > 8 + __m256i px = _mm256_cvtepu16_epi32( + _mm_loadu_si128((const __m128i*)(row + x1))); +#else + __m256i px = _mm256_cvtepu8_epi32( + _mm_loadl_epi64((const __m128i*)(row + x1))); +#endif + sumAcc = _mm256_add_epi32(sumAcc, px); + } + } + + __m128i lo128 = _mm256_castsi256_si128(sumAcc); + __m128i hi128 = _mm256_extracti128_si256(sumAcc, 1); + __m128i sum128 = _mm_add_epi32(lo128, hi128); + __m128i s64lo = _mm_cvtepi32_epi64(sum128); + __m128i s64hi = _mm_cvtepi32_epi64(_mm_srli_si128(sum128, 8)); + __m128i s64 = _mm_add_epi64(s64lo, s64hi); + int64_t sum_s[2]; + _mm_storeu_si128((__m128i*)sum_s, s64); + int64_t totalSum = sum_s[0] + sum_s[1]; + + const int N = blockSize * blockSize; + const double avg = (double)totalSum / N; + *avgOut = avg; + + const __m256d vavg = _mm256_set1_pd(avg); + __m256d varAcc = _mm256_setzero_pd(); + + for (int y1 = 0; y1 < blockSize; y1++) + { + const pixel* row = base + y1 * stride; + + for (int x1 = 0; x1 < blockSize; x1 += 8) + { +#if X265_DEPTH > 8 + __m128i px8 = _mm_loadu_si128((const __m128i*)(row + x1)); + /* Widen uint16 → int32, then split into two groups of 4 */ + __m256i px32 = _mm256_cvtepu16_epi32(px8); + __m128i lo4 = _mm256_castsi256_si128(px32); + __m128i hi4 = _mm256_extracti128_si256(px32, 1); +#else + __m128i px8 = _mm_loadl_epi64((const __m128i*)(row + x1)); + __m256i px32 = _mm256_cvtepu8_epi32(px8); + __m128i lo4 = _mm256_castsi256_si128(px32); + __m128i hi4 = _mm256_extracti128_si256(px32, 1); +#endif + __m256d dlo = _mm256_cvtepi32_pd(lo4); + __m256d dhi = _mm256_cvtepi32_pd(hi4); + + __m256d dlo_d = _mm256_sub_pd(dlo, vavg); + __m256d dhi_d = _mm256_sub_pd(dhi, vavg); + + varAcc = _mm256_add_pd(varAcc, _mm256_mul_pd(dlo_d, dlo_d)); + varAcc = _mm256_add_pd(varAcc, _mm256_mul_pd(dhi_d, dhi_d)); + } + } + + __m128d vlo2 = _mm256_castpd256_pd128(varAcc); + __m128d vhi2 = _mm256_extractf128_pd(varAcc, 1); + __m128d vsum = _mm_add_pd(vlo2, vhi2); + double varLanes[2]; + _mm_storeu_pd(varLanes, vsum); + *varianceOut = varLanes[0] + varLanes[1]; + } + /* Dispatch - overrides scalar defaults with SIMD variants at runtime */ void setupMCTFPrimitives_x86(MCSTFPrimitives & p, int cpuMask) { @@ -474,6 +552,7 @@ namespace X265_NS { { p.motionErrorLumaFrac = motionErrorLumaFrac_AVX2; p.applyMotion = applyMotion_AVX2; + p.lumaBlockAvgVariance = lumaBlockAvgVariance_AVX2; } } } /* namespace X265_NS */ From 5c9120461e8ffcc48ed421ae67b01826d1893b8f Mon Sep 17 00:00:00 2001 From: gunasrij Date: Mon, 29 Jun 2026 11:47:45 +0530 Subject: [PATCH 09/23] mcstf: Prune unused computations The m_useSADinME conditionals are no longer required because m_useSADinME is always set to 0, causing the SSD-based motion error path to be selected unconditionally. Remove the unused SAD branches and invoke the SSD path directly. Also remove the unused average/variance computation and the associated leastError adjustment, as they do not affect motion vector selection or the final encoding result. --- source/common/temporalfilter.cpp | 75 +++++--------------------------- 1 file changed, 11 insertions(+), 64 deletions(-) diff --git a/source/common/temporalfilter.cpp b/source/common/temporalfilter.cpp index c933002d3..e85e18f13 100644 --- a/source/common/temporalfilter.cpp +++ b/source/common/temporalfilter.cpp @@ -761,10 +761,7 @@ void MotionEstimatorTLD::motionEstimationLuma(MotionEstimatorTLD& m_metld, MV *m int mvIdx = testy * prevMvStride + testx; MV old = previous[mvIdx]; - if (m_useSADinME) - error = motionErrorLumaSAD(m_metld, src, stride, buf, blockX, blockY, old.x * factor, old.y * factor, blockSize, leastError); - else - error = motionErrorLumaSSD(m_metld, src, stride, buf, blockX, blockY, old.x * factor, old.y * factor, blockSize, leastError); + error = motionErrorLumaSSD(m_metld, src, stride, buf, blockX, blockY, old.x * factor, old.y * factor, blockSize, leastError); if (error < leastError) { @@ -775,10 +772,7 @@ void MotionEstimatorTLD::motionEstimationLuma(MotionEstimatorTLD& m_metld, MV *m } } - if (m_useSADinME) - error = motionErrorLumaSAD(m_metld, src, stride, buf, blockX, blockY, 0, 0, blockSize, leastError); - else - error = motionErrorLumaSSD(m_metld, src, stride, buf, blockX, blockY, 0, 0, blockSize, leastError); + error = motionErrorLumaSSD(m_metld, src, stride, buf, blockX, blockY, 0, 0, blockSize, leastError); if (error < leastError) { @@ -793,10 +787,8 @@ void MotionEstimatorTLD::motionEstimationLuma(MotionEstimatorTLD& m_metld, MV *m { for (int x2 = prevBest.x / m_motionVectorFactor - range; x2 <= prevBest.x / m_motionVectorFactor + range; x2++) { - if (m_useSADinME) - error = motionErrorLumaSAD(m_metld, src, stride, buf, blockX, blockY, x2 * m_motionVectorFactor, y2 * m_motionVectorFactor, blockSize, leastError); - else - error = motionErrorLumaSSD(m_metld, src, stride, buf, blockX, blockY, x2 * m_motionVectorFactor, y2 * m_motionVectorFactor, blockSize, leastError); + error = motionErrorLumaSSD(m_metld, src, stride, buf, blockX, blockY, x2 * m_motionVectorFactor, y2 * m_motionVectorFactor, blockSize, leastError); + if (error < leastError) { best.set(x2 * m_motionVectorFactor, y2 * m_motionVectorFactor); @@ -812,10 +804,7 @@ void MotionEstimatorTLD::motionEstimationLuma(MotionEstimatorTLD& m_metld, MV *m int idx = ((blockY / stepSize) * mvStride + (blockX - stepSize) / stepSize); MV leftMV = mvs[idx]; - if (m_useSADinME) - error = motionErrorLumaSAD(m_metld, src, stride, buf, blockX, blockY, leftMV.x, leftMV.y, blockSize, leastError); - else - error = motionErrorLumaSSD(m_metld, src, stride, buf, blockX, blockY, leftMV.x, leftMV.y, blockSize, leastError); + error = motionErrorLumaSSD(m_metld, src, stride, buf, blockX, blockY, leftMV.x, leftMV.y, blockSize, leastError); if (error < leastError) { @@ -824,30 +813,6 @@ void MotionEstimatorTLD::motionEstimationLuma(MotionEstimatorTLD& m_metld, MV *m } } - // calculate average - double avg = 0.0; - for (int x1 = 0; x1 < blockSize; x1++) - { - for (int y1 = 0; y1 < blockSize; y1++) - { - avg = avg + *(src + (blockX + x1 + stride * (blockY + y1))); - } - } - avg = avg / (blockSize * blockSize); - - // calculate variance - double variance = 0; - for (int x1 = 0; x1 < blockSize; x1++) - { - for (int y1 = 0; y1 < blockSize; y1++) - { - int pix = *(src + (blockX + x1 + stride * (blockY + y1))); - variance = variance + (pix - avg) * (pix - avg); - } - } - - leastError = (int)(20 * ((leastError + 5.0) / (variance + 5.0)) + (leastError / (blockSize * blockSize)) / 50); - int mvIdx = (blockY / stepSize) * mvStride + (blockX / stepSize); mvs[mvIdx] = best; } @@ -906,10 +871,7 @@ void MotionEstimatorTLD::motionEstimationLumaDoubleRes(MotionEstimatorTLD& m_met int mvIdx = testy * prevMvStride + testx; MV old = previous[mvIdx]; - if (m_useSADinME) - error = motionErrorLumaSAD(m_metld, orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, old.x * factor, old.y * factor, blockSize, leastError); - else - error = motionErrorLumaSSD(m_metld, orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, old.x * factor, old.y * factor, blockSize, leastError); + error = motionErrorLumaSSD(m_metld, orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, old.x * factor, old.y * factor, blockSize, leastError); if (error < leastError) { @@ -920,10 +882,7 @@ void MotionEstimatorTLD::motionEstimationLumaDoubleRes(MotionEstimatorTLD& m_met } } - if (m_useSADinME) - error = motionErrorLumaSAD(m_metld, orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, 0, 0, blockSize, leastError); - else - error = motionErrorLumaSSD(m_metld, orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, 0, 0, blockSize, leastError); + error = motionErrorLumaSSD(m_metld, orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, 0, 0, blockSize, leastError); if (error < leastError) { @@ -938,10 +897,7 @@ void MotionEstimatorTLD::motionEstimationLumaDoubleRes(MotionEstimatorTLD& m_met { for (int x2 = prevBest.x / m_motionVectorFactor - range; x2 <= prevBest.x / m_motionVectorFactor + range; x2++) { - if (m_useSADinME) - error = motionErrorLumaSAD(m_metld, orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, x2 * m_motionVectorFactor, y2 * m_motionVectorFactor, blockSize, leastError); - else - error = motionErrorLumaSSD(m_metld, orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, x2 * m_motionVectorFactor, y2 * m_motionVectorFactor, blockSize, leastError); + error = motionErrorLumaSSD(m_metld, orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, x2 * m_motionVectorFactor, y2 * m_motionVectorFactor, blockSize, leastError); if (error < leastError) { @@ -957,10 +913,7 @@ void MotionEstimatorTLD::motionEstimationLumaDoubleRes(MotionEstimatorTLD& m_met { for (int x2 = prevBest.x - doubleRange; x2 <= prevBest.x + doubleRange; x2++) { - if (m_useSADinME) - error = motionErrorLumaSAD(m_metld, orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, x2, y2, blockSize, leastError); - else - error = motionErrorLumaSSD(m_metld, orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, x2, y2, blockSize, leastError); + error = motionErrorLumaSSD(m_metld, orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, x2, y2, blockSize, leastError); if (error < leastError) { @@ -976,10 +929,7 @@ void MotionEstimatorTLD::motionEstimationLumaDoubleRes(MotionEstimatorTLD& m_met int idx = ((blockY - stepSize) / stepSize) * mvStride + (blockX / stepSize); MV aboveMV = mvs[idx]; - if (m_useSADinME) - error = motionErrorLumaSAD(m_metld, orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, aboveMV.x, aboveMV.y, blockSize, leastError); - else - error = motionErrorLumaSSD(m_metld, orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, aboveMV.x, aboveMV.y, blockSize, leastError); + error = motionErrorLumaSSD(m_metld, orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, aboveMV.x, aboveMV.y, blockSize, leastError); if (error < leastError) { @@ -993,10 +943,7 @@ void MotionEstimatorTLD::motionEstimationLumaDoubleRes(MotionEstimatorTLD& m_met int idx = ((blockY / stepSize) * mvStride + (blockX - stepSize) / stepSize); MV leftMV = mvs[idx]; - if (m_useSADinME) - error = motionErrorLumaSAD(m_metld, orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, leftMV.x, leftMV.y, blockSize, leastError); - else - error = motionErrorLumaSSD(m_metld, orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, leftMV.x, leftMV.y, blockSize, leastError); + error = motionErrorLumaSSD(m_metld, orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, leftMV.x, leftMV.y, blockSize, leastError); if (error < leastError) { From 479dcc2af2ba8d7584c5e9a6f28ff49141621bf0 Mon Sep 17 00:00:00 2001 From: gunasrij Date: Mon, 29 Jun 2026 13:18:43 +0530 Subject: [PATCH 10/23] mcstf: Generate lowres planes using HM-equivalent averaging Generate low-resolution planes using the HM averaging method only when MCSTF is enabled. Otherwise, preserve the existing low-res pipeline. --- source/common/lowres.cpp | 23 ++++++++++++++++------- source/common/lowres.h | 2 +- source/common/pixel.cpp | 25 +++++++++++++++++++++++++ source/encoder/slicetype.cpp | 2 +- 4 files changed, 43 insertions(+), 9 deletions(-) diff --git a/source/common/lowres.cpp b/source/common/lowres.cpp index 52953a812..57dfa2c89 100644 --- a/source/common/lowres.cpp +++ b/source/common/lowres.cpp @@ -26,6 +26,12 @@ #include "lowres.h" #include "mv.h" +namespace X265_NS { + // forward declaration - defined in pixel.cpp, no SIMD override for MCSTF path + void frame_init_lowres_core_mcstf(const pixel* src0, pixel* dst0, pixel* dsth, pixel* dstv, pixel* dstc, + intptr_t src_stride, intptr_t dst_stride, int width, int height); +} + using namespace X265_NS; /* @@ -344,7 +350,7 @@ void Lowres::destroy(x265_param* param) } } // (re) initialize lowres state -void Lowres::init(PicYuv *origPic, int poc) +void Lowres::init(PicYuv* origPic, int poc, bool bEnableTemporalFilter) { bLastMiniGopBFrame = false; bKeyframe = false; // Not a keyframe unless identified by lookahead @@ -380,9 +386,10 @@ void Lowres::init(PicYuv *origPic, int poc) plannedType[i] = X265_TYPE_AUTO; /* downscale and generate 4 hpel planes for lookahead */ - primitives.frameInitLowres(origPic->m_picOrg[0], - lowresPlane[0], lowresPlane[1], lowresPlane[2], lowresPlane[3], - origPic->m_stride, lumaStride, width, lines); + if (bEnableTemporalFilter) + frame_init_lowres_core_mcstf(origPic->m_picOrg[0], lowresPlane[0], lowresPlane[1], lowresPlane[2], lowresPlane[3], origPic->m_stride, lumaStride, width, lines); + else + primitives.frameInitLowres(origPic->m_picOrg[0], lowresPlane[0], lowresPlane[1], lowresPlane[2], lowresPlane[3], origPic->m_stride, lumaStride, width, lines); /* extend hpel planes for motion search */ extendPicBorder(lowresPlane[0], lumaStride, width, lines, origPic->m_lumaMarginX, origPic->m_lumaMarginY); @@ -392,9 +399,11 @@ void Lowres::init(PicYuv *origPic, int poc) if (origPic->m_param->bEnableHME || origPic->m_param->bEnableTemporalFilter) { - primitives.frameInitLowerRes(lowresPlane[0], - lowerResPlane[0], lowerResPlane[1], lowerResPlane[2], lowerResPlane[3], - lumaStride, lumaStride/2, (width / 2), (lines / 2)); + if (bEnableTemporalFilter) + frame_init_lowres_core_mcstf(lowresPlane[0], lowerResPlane[0], lowerResPlane[1], lowerResPlane[2], lowerResPlane[3], lumaStride, lumaStride / 2, (width / 2), (lines / 2)); + else + primitives.frameInitLowerRes(lowresPlane[0], lowerResPlane[0], lowerResPlane[1], lowerResPlane[2], lowerResPlane[3], lumaStride, lumaStride / 2, (width / 2), (lines / 2)); + extendPicBorder(lowerResPlane[0], lumaStride/2, width/2, lines/2, origPic->m_lumaMarginX/2, origPic->m_lumaMarginY/2); extendPicBorder(lowerResPlane[1], lumaStride/2, width/2, lines/2, origPic->m_lumaMarginX/2, origPic->m_lumaMarginY/2); extendPicBorder(lowerResPlane[2], lumaStride/2, width/2, lines/2, origPic->m_lumaMarginX/2, origPic->m_lumaMarginY/2); diff --git a/source/common/lowres.h b/source/common/lowres.h index f4b843f5e..2d29830a8 100644 --- a/source/common/lowres.h +++ b/source/common/lowres.h @@ -253,7 +253,7 @@ struct Lowres : public ReferencePlanes bool create(x265_param* param, PicYuv *origPic, uint32_t qgSize); void destroy(x265_param* param); - void init(PicYuv *origPic, int poc); + void init(PicYuv* origPic, int poc, bool bEnableTemporalFilter); }; } diff --git a/source/common/pixel.cpp b/source/common/pixel.cpp index 06e5732bd..d850f4217 100644 --- a/source/common/pixel.cpp +++ b/source/common/pixel.cpp @@ -1030,6 +1030,31 @@ static pixel planeClipAndMax_c(pixel *src, intptr_t stride, int width, int heigh namespace X265_NS { // x265 private namespace +/* Scalar lowres downscale for MCSTF path - HM-equivalent filter, no SIMD override */ + void frame_init_lowres_core_mcstf(const pixel* src0, pixel* dst0, pixel* dsth, pixel* dstv, pixel* dstc, + intptr_t src_stride, intptr_t dst_stride, int width, int height) + { + for (int y = 0; y < height; y++) + { + const pixel* src1 = src0 + src_stride; + const pixel* src2 = src1 + src_stride; + for (int x = 0; x < width; x++) + { +#define FILTER(a, b, c, d) ((a + b + c + d + 2) >> 2) + dst0[x] = FILTER(src0[2 * x], src1[2 * x], src0[2 * x + 1], src1[2 * x + 1]); + dsth[x] = FILTER(src0[2 * x + 1], src1[2 * x + 1], src0[2 * x + 2], src1[2 * x + 2]); + dstv[x] = FILTER(src1[2 * x], src2[2 * x], src1[2 * x + 1], src2[2 * x + 1]); + dstc[x] = FILTER(src1[2 * x + 1], src2[2 * x + 1], src1[2 * x + 2], src2[2 * x + 2]); +#undef FILTER + } + src0 += src_stride * 2; + dst0 += dst_stride; + dsth += dst_stride; + dstv += dst_stride; + dstc += dst_stride; + } + } + /* Extend the edges of a picture so that it may safely be used for motion * compensation. This function assumes the picture is stored in a buffer with * sufficient padding for the X and Y margins */ diff --git a/source/encoder/slicetype.cpp b/source/encoder/slicetype.cpp index 2956973f3..39ad0d7f0 100644 --- a/source/encoder/slicetype.cpp +++ b/source/encoder/slicetype.cpp @@ -1755,7 +1755,7 @@ void PreLookaheadGroup::processTasks(int workerThreadID) ProfileLookaheadTime(m_lookahead.m_preLookaheadElapsedTime, m_lookahead.m_countPreLookahead); ProfileScopeEvent(prelookahead); m_lock.release(); - preFrame->m_lowres.init(preFrame->m_fencPic, preFrame->m_poc); + preFrame->m_lowres.init(preFrame->m_fencPic, preFrame->m_poc, m_lookahead.m_param->bEnableTemporalFilter); if (m_lookahead.m_bAdaptiveQuant) tld.calcAdaptiveQuantFrame(preFrame, m_lookahead.m_param); From cb39dfae56171bb5466e09ae413f6d0aecb65066 Mon Sep 17 00:00:00 2001 From: gunasrij Date: Mon, 29 Jun 2026 14:33:33 +0530 Subject: [PATCH 11/23] Remove unused frame_subsample_luma primitive --- source/common/loongarch64/asm-primitives.cpp | 2 - source/common/loongarch64/mc-a2.S | 117 ------------------- source/common/loongarch64/pixel.h | 2 - source/common/pixel.cpp | 19 --- source/common/primitives.h | 3 +- source/common/x86/asm-primitives.cpp | 14 +-- source/test/pixelharness.cpp | 15 --- 7 files changed, 2 insertions(+), 170 deletions(-) diff --git a/source/common/loongarch64/asm-primitives.cpp b/source/common/loongarch64/asm-primitives.cpp index 6f88d2bd5..94e6ff4ec 100644 --- a/source/common/loongarch64/asm-primitives.cpp +++ b/source/common/loongarch64/asm-primitives.cpp @@ -936,7 +936,6 @@ void setupAssemblyPrimitives(EncoderPrimitives &p, int cpuMask) // mc p.frameInitLowres = PFX(frame_init_lowres_core_lsx); p.frameInitLowerRes = PFX(frame_init_lowres_core_lsx); - p.frameSubSampleLuma = PFX(frame_subsample_luma_lsx); p.fix8Unpack = PFX(cutree_fix8_unpack_lsx); p.fix8Pack = PFX(cutree_fix8_pack_lsx); @@ -1333,7 +1332,6 @@ void setupAssemblyPrimitives(EncoderPrimitives &p, int cpuMask) // mc p.frameInitLowres = PFX(frame_init_lowres_core_lasx); p.frameInitLowerRes = PFX(frame_init_lowres_core_lasx); - p.frameSubSampleLuma = PFX(frame_subsample_luma_lasx); p.fix8Unpack = PFX(cutree_fix8_unpack_lasx); p.fix8Pack = PFX(cutree_fix8_pack_lasx); diff --git a/source/common/loongarch64/mc-a2.S b/source/common/loongarch64/mc-a2.S index 8b3b37c9e..78fb6d8c5 100644 --- a/source/common/loongarch64/mc-a2.S +++ b/source/common/loongarch64/mc-a2.S @@ -278,123 +278,6 @@ function x265_frame_init_lowres_core_lasx blt zero, t0, .LOOP_HEIGHT_FRAME_INIT_LOWRES_CORE_LASX endfunc -.macro CALCULATE_FRAMESUBSAMPLELUMA_LSX_8 offset0 - // load inRow[offset0 ~ offset0+15], inRowBelow[offset0 ~ offset0+15] - vldx vr1, a0, \offset0 - vldx vr3, t0, \offset0 - vaddwev.h.bu vr4, vr1, vr3 - vaddwod.h.bu vr5, vr1, vr3 - vsrari.h vr4, vr4, 1 - vsrari.h vr5, vr5, 1 - vadd.h vr6, vr4, vr5 - vsrarni.b.h vr6, vr6, 1 -.endm - -/*----------------------------------------------------------------------------- -void x265_frame_subsample_luma_lsx(const pixel* src0, pixel* dst0, - intptr_t src_stride, intptr_t dst_stride, int width, int height) ------------------------------------------------------------------------------*/ -function x265_frame_subsample_luma_lsx - andi t4, a4, 7 // width % 8 - srli.d a4, a4, 3 // width / 8 - -.LOOP_HEIGHT_FRAME_SUBSAMPLE_LUMA_LSX: - add.d t0, a0, a2 // *inRowBelow - addi.d t1, zero, 0 - addi.d t2, zero, 0 - addi.d t8, zero, 0 // index - -.LOOP_WIDTH_FRAME_SUBSAMPLE_LUMA_LSX: - bge t8, a4, .HANDLING_MANTISSA_FRAMESUBSAMPLELUMA_LSX_7 - CALCULATE_FRAMESUBSAMPLELUMA_LSX_8 t1 - add.d t3, a1, t2 - vstelm.d vr6, t3, 0, 0 - - // prepare for next inner-loop - addi.d t1, t1, 16 - addi.d t2, t2, 8 - addi.d t8, t8, 1 // index++ - b .LOOP_WIDTH_FRAME_SUBSAMPLE_LUMA_LSX - -.HANDLING_MANTISSA_FRAMESUBSAMPLELUMA_LSX_7: - bge zero, t4, .PREPARE_FOR_NEXT_OUTERLOOP_FRAMESUBSAMPLELUMA_LSX - CALCULATE_FRAMESUBSAMPLELUMA_LSX_8 t1 // calculate mantissa - addi.d t8, zero, 0 - add.d t3, a1, t2 -.irp offset0, 0, 1, 2, 3, 4, 5, 6 - vstelm.b vr6, t3, 0, \offset0 - addi.d t8, t8, 1 - addi.d t3, t3, 1 - bge t8, t4, .PREPARE_FOR_NEXT_OUTERLOOP_FRAMESUBSAMPLELUMA_LSX -.endr - -.PREPARE_FOR_NEXT_OUTERLOOP_FRAMESUBSAMPLELUMA_LSX: - // prepare for next outer-loop - add.d a0, a0, a2 - add.d a0, a0, a2 - add.d a1, a1, a3 - addi.d a5, a5, -1 - blt zero, a5, .LOOP_HEIGHT_FRAME_SUBSAMPLE_LUMA_LSX -endfunc - -.macro CALCULATE_FRAMESUBSAMPLELUMA_LASX_16 offset0 - // load inRow[offset0 ~ offset0+31], inRowBelow[offset0 ~ offset0+31] - xvldx xr1, a0, \offset0 - xvldx xr3, t0, \offset0 - xvaddwev.h.bu xr4, xr1, xr3 - xvaddwod.h.bu xr5, xr1, xr3 - xvsrari.h xr4, xr4, 1 - xvsrari.h xr5, xr5, 1 - xvadd.h xr6, xr4, xr5 - xvsrarni.b.h xr6, xr6, 1 - xvpermi.d xr6, xr6, 0b00001000 -.endm - -/*----------------------------------------------------------------------------- -void x265_frame_subsample_luma_lasx(const pixel* src0, pixel* dst0, - intptr_t src_stride, intptr_t dst_stride, int width, int height) ------------------------------------------------------------------------------*/ -function x265_frame_subsample_luma_lasx - andi t4, a4, 15 // width % 16 - srli.d a4, a4, 4 // width / 16 - -.LOOP_HEIGHT_FRAME_SUBSAMPLE_LUMA_LASX: - add.d t0, a0, a2 // *inRowBelow - addi.d t1, zero, 0 - addi.d t2, zero, 0 - addi.d t8, zero, 0 // index - -.LOOP_WIDTH_FRAME_SUBSAMPLE_LUMA_LASX: - bge t8, a4, .HANDLING_MANTISSA_FRAMESUBSAMPLELUMA_LASX_15 - CALCULATE_FRAMESUBSAMPLELUMA_LASX_16 t1 - vstx vr6, a1, t2 - addi.d t1, t1, 32 - addi.d t2, t2, 16 - addi.d t8, t8, 1 // index++ - b .LOOP_WIDTH_FRAME_SUBSAMPLE_LUMA_LASX - -.HANDLING_MANTISSA_FRAMESUBSAMPLELUMA_LASX_15: - bge zero, t4, .PREPARE_FOR_NEXT_OUTERLOOP_FRAMESUBSAMPLELUMA_LASX - CALCULATE_FRAMESUBSAMPLELUMA_LASX_16 t1 // calculate mantissa - addi.d t8, zero, 0 - add.d t3, a1, t2 -.irp offset0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 - vstelm.b vr6, t3, 0, \offset0 - addi.d t8, t8, 1 - addi.d t3, t3, 1 - bge t8, t4, .PREPARE_FOR_NEXT_OUTERLOOP_FRAMESUBSAMPLELUMA_LASX -.endr - -.PREPARE_FOR_NEXT_OUTERLOOP_FRAMESUBSAMPLELUMA_LASX: - // prepare for next loop - add.d a0, a0, a2 - add.d a0, a0, a2 - add.d a1, a1, a3 - addi.d a5, a5, -1 - blt zero, a5, .LOOP_HEIGHT_FRAME_SUBSAMPLE_LUMA_LASX -endfunc - - .macro HANDLING_MANTISSA_MC_A2_3 size, dst0, dst1, dst2, count, dst_adress, \ dst_stride, index0, index1, index2, terminate_lable addi.d \count, \count, -1 diff --git a/source/common/loongarch64/pixel.h b/source/common/loongarch64/pixel.h index 731664e70..9da01c6d5 100644 --- a/source/common/loongarch64/pixel.h +++ b/source/common/loongarch64/pixel.h @@ -372,8 +372,6 @@ void x265_addAvg_64x64_lasx(const int16_t* src0, const int16_t* src1, pixel* dst void x265_frame_init_lowres_core_lsx(const pixel *src0, pixel *dst0, pixel *dsth, pixel *dstv, pixel *dstc, intptr_t src_stride, intptr_t dst_stride, int width, int height); void x265_frame_init_lowres_core_lasx(const pixel *src0, pixel *dst0, pixel *dsth, pixel *dstv, pixel *dstc, intptr_t src_stride, intptr_t dst_stride, int width, int height); -void x265_frame_subsample_luma_lsx(const pixel *src0, pixel *dst0, intptr_t src_stride, intptr_t dst_stride, int width, int height); -void x265_frame_subsample_luma_lasx(const pixel *src0, pixel *dst0, intptr_t src_stride, intptr_t dst_stride, int width, int height); void x265_cutree_fix8_unpack_lsx(double *dst, uint16_t *src, int count); void x265_cutree_fix8_unpack_lasx(double *dst, uint16_t *src, int count); void x265_cutree_fix8_pack_lsx(uint16_t *dst, double *src, int count); diff --git a/source/common/pixel.cpp b/source/common/pixel.cpp index d850f4217..04543434b 100644 --- a/source/common/pixel.cpp +++ b/source/common/pixel.cpp @@ -619,23 +619,6 @@ void frame_init_lowres_core(const pixel* src0, pixel* dst0, pixel* dsth, pixel* } } -static -void frame_subsample_luma(const pixel* src0, pixel* dst0, intptr_t src_stride, intptr_t dst_stride, int width, int height) -{ - for (int y = 0; y < height; y++, src0 += 2 * src_stride, dst0 += dst_stride) - { - const pixel *inRow = src0; - const pixel *inRowBelow = src0 + src_stride; - pixel *target = dst0; - for (int x = 0; x < width; x++) - { - target[x] = (((inRow[0] + inRowBelow[0] + 1) >> 1) + ((inRow[1] + inRowBelow[1] + 1) >> 1) + 1) >> 1; - inRow += 2; - inRowBelow += 2; - } - } -} - /* structural similarity metric */ static void ssim_4x4x2_core(const pixel* pix1, intptr_t stride1, const pixel* pix2, intptr_t stride2, int sums[2][4]) { @@ -1389,7 +1372,5 @@ void setupPixelPrimitives_c(EncoderPrimitives &p) p.cu[BLOCK_16x16].normFact = normFact_c; p.cu[BLOCK_32x32].normFact = normFact_c; p.cu[BLOCK_64x64].normFact = normFact_c; - /* SubSample Luma*/ - p.frameSubSampleLuma = frame_subsample_luma; } } diff --git a/source/common/primitives.h b/source/common/primitives.h index fd343f5fe..16a4c6e02 100644 --- a/source/common/primitives.h +++ b/source/common/primitives.h @@ -355,8 +355,7 @@ struct EncoderPrimitives downscale_t frameInitLowres; downscale_t frameInitLowerRes; - /* Sub Sample Luma */ - downscaleluma_t frameSubSampleLuma; + cutree_propagate_cost propagateCost; cutree_fix8_unpack fix8Unpack; cutree_fix8_pack fix8Pack; diff --git a/source/common/x86/asm-primitives.cpp b/source/common/x86/asm-primitives.cpp index bb32f32cd..72a207862 100644 --- a/source/common/x86/asm-primitives.cpp +++ b/source/common/x86/asm-primitives.cpp @@ -1091,7 +1091,7 @@ void setupAssemblyPrimitives(EncoderPrimitives &p, int cpuMask) // Main10 p.frameInitLowres = PFX(frame_init_lowres_core_sse2); p.frameInitLowerRes = PFX(frame_init_lowres_core_sse2); - p.frameSubSampleLuma = PFX(frame_subsample_luma_sse2); + // TODO: the planecopy_sp is really planecopy_SC now, must be fix it //p.planecopy_sp = PFX(downShift_16_sse2); p.planecopy_sp_shl = PFX(upShift_16_sse2); @@ -1122,7 +1122,6 @@ void setupAssemblyPrimitives(EncoderPrimitives &p, int cpuMask) // Main10 { ASSIGN2(p.scale1D_128to64, scale1D_128to64_ssse3); p.scale2D_64to32 = PFX(scale2D_64to32_ssse3); - p.frameSubSampleLuma = PFX(frame_subsample_luma_ssse3); // p.pu[LUMA_4x4].satd = p.cu[BLOCK_4x4].sa8d = PFX(pixel_satd_4x4_ssse3); this one is broken ALL_LUMA_PU(satd, pixel_satd, ssse3); @@ -1464,7 +1463,6 @@ void setupAssemblyPrimitives(EncoderPrimitives &p, int cpuMask) // Main10 p.pu[LUMA_64x48].copy_pp = (copy_pp_t)PFX(blockcopy_ss_64x48_avx); p.pu[LUMA_64x64].copy_pp = (copy_pp_t)PFX(blockcopy_ss_64x64_avx); p.propagateCost = PFX(mbtree_propagate_cost_avx); - p.frameSubSampleLuma = PFX(frame_subsample_luma_avx); } if (cpuMask & X265_CPU_XOP) { @@ -1476,7 +1474,6 @@ void setupAssemblyPrimitives(EncoderPrimitives &p, int cpuMask) // Main10 LUMA_VAR(xop); p.frameInitLowres = PFX(frame_init_lowres_core_xop); p.frameInitLowerRes = PFX(frame_init_lowres_core_xop); - p.frameSubSampleLuma = PFX(frame_subsample_luma_xop); } if (cpuMask & X265_CPU_AVX2) { @@ -2306,8 +2303,6 @@ void setupAssemblyPrimitives(EncoderPrimitives &p, int cpuMask) // Main10 p.frameInitLowres = PFX(frame_init_lowres_core_avx2); p.frameInitLowerRes = PFX(frame_init_lowres_core_avx2); - p.frameSubSampleLuma = PFX(frame_subsample_luma_avx2); - p.propagateCost = PFX(mbtree_propagate_cost_avx2); p.fix8Unpack = PFX(cutree_fix8_unpack_avx2); p.fix8Pack = PFX(cutree_fix8_pack_avx2); @@ -3307,7 +3302,6 @@ void setupAssemblyPrimitives(EncoderPrimitives &p, int cpuMask) // Main //p.frameInitLowres = PFX(frame_init_lowres_core_mmx2); p.frameInitLowres = PFX(frame_init_lowres_core_sse2); p.frameInitLowerRes = PFX(frame_init_lowres_core_sse2); - p.frameSubSampleLuma = PFX(frame_subsample_luma_sse2); ALL_LUMA_TU(blockfill_s[NONALIGNED], blockfill_s, sse2); ALL_LUMA_TU(blockfill_s[ALIGNED], blockfill_s, sse2); @@ -3432,8 +3426,6 @@ void setupAssemblyPrimitives(EncoderPrimitives &p, int cpuMask) // Main ASSIGN2(p.scale1D_128to64, scale1D_128to64_ssse3); p.scale2D_64to32 = PFX(scale2D_64to32_ssse3); - p.frameSubSampleLuma = PFX(frame_subsample_luma_ssse3); - ASSIGN2(p.pu[LUMA_8x4].convert_p2s, filterPixelToShort_8x4_ssse3); ASSIGN2(p.pu[LUMA_8x8].convert_p2s, filterPixelToShort_8x8_ssse3); ASSIGN2(p.pu[LUMA_8x16].convert_p2s, filterPixelToShort_8x16_ssse3); @@ -3701,7 +3693,6 @@ void setupAssemblyPrimitives(EncoderPrimitives &p, int cpuMask) // Main p.frameInitLowres = PFX(frame_init_lowres_core_avx); p.frameInitLowerRes = PFX(frame_init_lowres_core_avx); p.propagateCost = PFX(mbtree_propagate_cost_avx); - p.frameSubSampleLuma = PFX(frame_subsample_luma_avx); } if (cpuMask & X265_CPU_XOP) { @@ -3713,7 +3704,6 @@ void setupAssemblyPrimitives(EncoderPrimitives &p, int cpuMask) // Main p.cu[BLOCK_16x16].sse_pp = PFX(pixel_ssd_16x16_xop); p.frameInitLowres = PFX(frame_init_lowres_core_xop); p.frameInitLowerRes = PFX(frame_init_lowres_core_xop); - p.frameSubSampleLuma = PFX(frame_subsample_luma_xop); } #if X86_64 @@ -4696,8 +4686,6 @@ void setupAssemblyPrimitives(EncoderPrimitives &p, int cpuMask) // Main p.saoCuStatsE2 = PFX(saoCuStatsE2_avx2); p.saoCuStatsE3 = PFX(saoCuStatsE3_avx2); - p.frameSubSampleLuma = PFX(frame_subsample_luma_avx2); - if (cpuMask & X265_CPU_BMI2) { p.scanPosLast = PFX(scanPosLast_avx2_bmi2); diff --git a/source/test/pixelharness.cpp b/source/test/pixelharness.cpp index bdda926d9..13a20983b 100644 --- a/source/test/pixelharness.cpp +++ b/source/test/pixelharness.cpp @@ -2845,15 +2845,6 @@ bool PixelHarness::testCorrectness(const EncoderPrimitives& ref, const EncoderPr } } - if (opt.frameSubSampleLuma) - { - if (!check_downscaleluma_t(ref.frameSubSampleLuma, opt.frameSubSampleLuma)) - { - printf("SubSample Luma failed!\n"); - return false; - } - } - if (opt.scale1D_128to64[NONALIGNED]) { if (!check_scale1D_pp(ref.scale1D_128to64[NONALIGNED], opt.scale1D_128to64[NONALIGNED])) @@ -3569,12 +3560,6 @@ void PixelHarness::measureSpeed(const EncoderPrimitives& ref, const EncoderPrimi REPORT_SPEEDUP(opt.frameInitLowres, ref.frameInitLowres, pbuf2, pbuf1, pbuf2, pbuf3, pbuf4, 64, 64, 64, 64); } - if (opt.frameSubSampleLuma) - { - HEADER0("downscaleluma"); - REPORT_SPEEDUP(opt.frameSubSampleLuma, ref.frameSubSampleLuma, pbuf2, pbuf1, 64, 64, 64, 64); - } - if (opt.scale1D_128to64[NONALIGNED]) { HEADER0("scale1D_128to64"); From 4a63489333fb9f6fccf73a58032e60e89751aaed Mon Sep 17 00:00:00 2001 From: gunasrij Date: Mon, 29 Jun 2026 15:25:21 +0530 Subject: [PATCH 12/23] mcstf: Add AVX2 implementation for bilateralFilter --- source/common/temporalfilter.cpp | 223 +++++++++++----------- source/common/temporalfilter.h | 27 ++- source/common/x86/temporalfilter_simd.cpp | 191 +++++++++++++++++- 3 files changed, 308 insertions(+), 133 deletions(-) diff --git a/source/common/temporalfilter.cpp b/source/common/temporalfilter.cpp index e85e18f13..32c0836fe 100644 --- a/source/common/temporalfilter.cpp +++ b/source/common/temporalfilter.cpp @@ -181,6 +181,73 @@ namespace X265_NS { } } + static void computeBlockStats_c( + const pixel* srcPel, intptr_t srcStride, + const pixel* refPel, intptr_t refStride, + int blkSize, int* outVariance, int* outDiffsum) + { + int variance = 0, diffsum = 0; + for (int y1 = 0; y1 < blkSize; y1++) + { + for (int x1 = 0; x1 < blkSize; x1++) + { + int diff = *(srcPel + srcStride * y1 + x1) + - *(refPel + refStride * y1 + x1); + variance += diff * diff; + if (x1 != blkSize - 1) + { + int diffR = *(srcPel + srcStride * y1 + x1 + 1) + - *(refPel + refStride * y1 + x1 + 1); + diffsum += (diffR - diff) * (diffR - diff); + } + if (y1 != blkSize - 1) + { + int diffD = *(srcPel + srcStride * (y1 + 1) + x1) + - *(refPel + refStride * (y1 + 1) + x1); + diffsum += (diffD - diff) * (diffD - diff); + } + } + } + *outVariance = variance; + *outDiffsum = diffsum; + } + + static void bilateralWeightedFilter_c( + const pixel* srcBlk, intptr_t srcStride, + int numRefs, + const pixel* const* refBlks, + const intptr_t* refStrides, + const double* vww, + const double* vsw, + double bdw, + double maxSample, + int blkSize, + pixel* dstBlk, intptr_t dstStride) + { + for (int y = 0; y < blkSize; y++) + { + for (int x = 0; x < blkSize; x++) + { + const int orgVal = (int)srcBlk[y * srcStride + x]; + double temporalWeightSum = 1.0; + double newVal = (double)orgVal; + for (int i = 0; i < numRefs; i++) + { + const int refVal = (int)refBlks[i][y * refStrides[i] + x]; + double diff = (double)(refVal - orgVal) * bdw; + double diffSq = diff * diff; + const double weight = vww[i] * exp(-diffSq / vsw[i]); + newVal += weight * refVal; + temporalWeightSum += weight; + } + newVal /= temporalWeightSum; + double sampleVal = round(newVal); + sampleVal = (sampleVal < 0 ? 0 : (sampleVal > maxSample ? maxSample : sampleVal)); + dstBlk[y * dstStride + x] = (pixel)sampleVal; + } + } + } + /* Global MCSTF primitives table */ MCSTFPrimitives mcstfPrim; @@ -188,6 +255,8 @@ namespace X265_NS { { p.motionErrorLumaFrac = motionErrorLumaFrac_c; p.applyMotion = applyMotion_c; + p.computeBlockStats = computeBlockStats_c; + p.bilateralWeightedFilter = bilateralWeightedFilter_c; } }/* namespace X265_NS */ @@ -521,166 +590,96 @@ void TemporalFilter::bilateralFilter_core(Frame* frame, TemporalFilterRefPicInfo const double lumaSigmaSq = (m_QP - m_sigmaZeroPoint) * (m_QP - m_sigmaZeroPoint) * m_sigmaMultiplier; const double chromaSigmaSq = 30 * 30; - PicYuv* orgPic = frame->m_fencPic; for (int i = 0; i < numRefs; i++) { - TemporalFilterRefPicInfo *ref = &m_mcstfRefList[i]; + TemporalFilterRefPicInfo* ref = &m_mcstfRefList[i]; applyMotion(m_mcstfRefList[i].mvs, m_mcstfRefList[i].mvsStride, m_mcstfRefList[i].picBuffer, ref->compensatedPic, blockRow, rowSize); } for (int c = 0; c < m_numComponents; c++) { - int height, width; - pixel *srcPelPlane = NULL; - intptr_t srcStride, correctedPicsStride = 0; - - int csx = (!c) ? 0 : CHROMA_H_SHIFT(m_internalCsp); - int csy = (!c) ? 0 : CHROMA_V_SHIFT(m_internalCsp); - if (!c) - { - height = orgPic->m_picHeight; - width = orgPic->m_picWidth; - srcPelPlane = orgPic->m_picOrg[c]; - srcStride = orgPic->m_stride; - } - else - { - - height = orgPic->m_picHeight >> csy; - width = orgPic->m_picWidth >> csx; - srcPelPlane = orgPic->m_picOrg[c]; - srcStride = (int)orgPic->m_strideC; - } - - const double sigmaSq = (!c) ? lumaSigmaSq : chromaSigmaSq; - const double weightScaling = overallStrength * ( (!c) ? 0.4 : m_chromaFactor); - + const int csx = (!c) ? 0 : CHROMA_H_SHIFT(m_internalCsp); + const int csy = (!c) ? 0 : CHROMA_V_SHIFT(m_internalCsp); + const int height = (!c) ? orgPic->m_picHeight : orgPic->m_picHeight >> csy; + const int width = (!c) ? orgPic->m_picWidth : orgPic->m_picWidth >> csx; + pixel* srcPelPlane = orgPic->m_picOrg[c]; + const intptr_t srcStride = (!c) ? orgPic->m_stride : (intptr_t)orgPic->m_strideC; + + const double sigmaSq = (!c) ? lumaSigmaSq : chromaSigmaSq; + const double weightScaling = overallStrength * ((!c) ? 0.4 : m_chromaFactor); const double maxSampleValue = (1 << m_bitDepth) - 1; const double bitDepthDiffWeighting = 1024.0 / (maxSampleValue + 1); - const int blkSize = (!c) ? 8 : 4; - const int vShift = (!c) ? 0 : csy; + const int vShift = (!c) ? 0 : csy; const int planeRowStart = (blockRow * rowSize) >> vShift; - const int planeRowEnd = X265_MIN((blockRow * rowSize + rowSize) >> vShift, height); - + const int planeRowEnd = X265_MIN((blockRow * rowSize + rowSize) >> vShift, height); const int blkRowStart = (planeRowStart / blkSize) * blkSize; - const int blkRowEnd = X265_MIN(((planeRowEnd + blkSize - 1) / blkSize) * blkSize, height); + const int blkRowEnd = X265_MIN(((planeRowEnd + blkSize - 1) / blkSize) * blkSize, height); for (int by = blkRowStart; by + blkSize <= blkRowEnd; by += blkSize) { for (int bx = 0; bx + blkSize <= width; bx += blkSize) { - double vww [16] = {}; - double vsw [16] = {}; - const pixel* srcPel = srcPelPlane + by * srcStride + bx; + // Step 1: noise computation via SIMD primitive double minError = 9999999; - for (int i = 0; i < numRefs; i++) { - TemporalFilterRefPicInfo *refPicInfo = &m_mcstfRefList[i]; - - if (!c) - correctedPicsStride = refPicInfo->compensatedPic->m_stride; - else - correctedPicsStride = refPicInfo->compensatedPic->m_strideC; - - double variance = 0, diffsum = 0; - const pixel *refPel = refPicInfo->compensatedPic->m_picOrg[c] + by * correctedPicsStride + bx; - for (int y1 = 0; y1 < blkSize; y1++) - { - for (int x1 = 0; x1 < blkSize; x1++) - { - int pix = *(srcPel + srcStride * y1 + x1); - int ref = *(refPel + correctedPicsStride * y1 + x1); - int diff = pix - ref; + TemporalFilterRefPicInfo* refPicInfo = &m_mcstfRefList[i]; + const intptr_t refStride = (!c) ? refPicInfo->compensatedPic->m_stride + : refPicInfo->compensatedPic->m_strideC; + const pixel* refPel = refPicInfo->compensatedPic->m_picOrg[c] + + by * refStride + bx; - variance += diff * diff; + int iVariance = 0, iDiffsum = 0; + mcstfPrim.computeBlockStats( + srcPel, srcStride, refPel, refStride, + blkSize, &iVariance, &iDiffsum); - if (x1 != blkSize - 1) - { - int pixR = *(srcPel + srcStride * y1 + x1 + 1); - int refR = *(refPel + correctedPicsStride * y1 + x1 + 1); - int diffR = pixR - refR; - diffsum += (diffR - diff) * (diffR - diff); - } - if (y1 != blkSize - 1) - { - int pixD = *(srcPel + srcStride * y1 + x1 + srcStride); - int refD = *(refPel + correctedPicsStride * y1 + x1 + correctedPicsStride); - int diffD = pixD - refD; - diffsum += (diffD - diff) * (diffD - diff); - } - } - } const int cntV = blkSize * blkSize; const int cntD = 2 * cntV - blkSize - blkSize; - refPicInfo->noise[(by / blkSize) * refPicInfo->mvsStride + (bx / blkSize)] = (int)round((15.0 * cntD / cntV * variance + 5.0) / (diffsum + 5.0)); - minError = X265_MIN(minError, (double)refPicInfo->error[(by / blkSize) * refPicInfo->mvsStride + (bx / blkSize)]); + refPicInfo->noise[(by / blkSize) * refPicInfo->mvsStride + (bx / blkSize)] = + (int)round((15.0 * cntD / cntV * iVariance + 5.0) / (iDiffsum + 5.0)); + minError = X265_MIN(minError, + (double)refPicInfo->error[(by / blkSize) * refPicInfo->mvsStride + (bx / blkSize)]); } - // ── Step 2: pre-compute vww / vsw per reference (block-level) ─ + // Step 2: pre-compute vww / vsw (block-level) + double vww[MCTF_MAX_REFS] = {}; + double vsw[MCTF_MAX_REFS] = {}; for (int i = 0; i < numRefs; i++) { TemporalFilterRefPicInfo* refPicInfo = &m_mcstfRefList[i]; - const int error = refPicInfo->error[(by / blkSize) * refPicInfo->mvsStride + (bx / blkSize)]; const int noise = refPicInfo->noise[(by / blkSize) * refPicInfo->mvsStride + (bx / blkSize)]; - const int index = X265_MIN(3, std::abs(refPicInfo->origOffset) - 1); - double ww = 1, sw = 1; - ww *= (noise < 25) ? 1 : 1.2; + ww *= (noise < 25) ? 1 : 1.2; sw *= (noise < 25) ? 1.3 : 0.8; ww *= (error < 50) ? 1.2 : ((error > 100) ? 0.8 : 1); sw *= (error < 50) ? 1.3 : 1; ww *= ((minError + 1) / (error + 1)); - vww[i] = weightScaling * s_refStrengths[refStrengthRow][index] * ww; vsw[i] = 2 * sw * sigmaSq; } - // ── Step 3: pixel loop uses pre-computed vww / vsw ────── - for (int y = by; y < X265_MIN(by + blkSize, height); y++) + // Step 3: pixel filtering via SIMD primitive + const pixel* refBlkPtrs[MCTF_MAX_REFS]; + intptr_t refBlkStrides[MCTF_MAX_REFS]; + for (int i = 0; i < numRefs; i++) { - for (int x = bx; x < X265_MIN(bx + blkSize, width); x++) - { - const int orgVal = (int)srcPelPlane[y * srcStride + x]; - double temporalWeightSum = 1.0; - double newVal = (double)orgVal; - - for (int i = 0; i < numRefs; i++) - { - TemporalFilterRefPicInfo* refPicInfo = &m_mcstfRefList[i]; - - correctedPicsStride = (!c) ? refPicInfo->compensatedPic->m_stride - : refPicInfo->compensatedPic->m_strideC; - - const pixel* pCorrectedPelPtr = refPicInfo->compensatedPic->m_picOrg[c] - + y * correctedPicsStride + x; - const int refVal = (int)*pCorrectedPelPtr; - - double diff = (double)(refVal - orgVal); - diff *= bitDepthDiffWeighting; - double diffSq = diff * diff; - - const double weight = vww[i] * exp(-diffSq / vsw[i]); - - newVal += weight * refVal; - temporalWeightSum += weight; - } - - newVal /= temporalWeightSum; - double sampleVal = round(newVal); - sampleVal = (sampleVal < 0 ? 0 : (sampleVal > maxSampleValue ? maxSampleValue : sampleVal)); - srcPelPlane[y * srcStride + x] = (pixel)sampleVal; - } + TemporalFilterRefPicInfo* refPicInfo = &m_mcstfRefList[i]; + refBlkStrides[i] = (!c) ? refPicInfo->compensatedPic->m_stride + : refPicInfo->compensatedPic->m_strideC; + refBlkPtrs[i] = refPicInfo->compensatedPic->m_picOrg[c] + + by * refBlkStrides[i] + bx; } - + mcstfPrim.bilateralWeightedFilter(srcPel, srcStride, numRefs, refBlkPtrs, refBlkStrides, vww, vsw, bitDepthDiffWeighting, maxSampleValue, + blkSize, srcPelPlane + by * srcStride + bx, srcStride); } } } diff --git a/source/common/temporalfilter.h b/source/common/temporalfilter.h index e1a62c859..584abd026 100644 --- a/source/common/temporalfilter.h +++ b/source/common/temporalfilter.h @@ -70,21 +70,20 @@ namespace X265_NS { struct MCSTFPrimitives { - int (*motionErrorLumaFrac)(const pixel* origOrigin, intptr_t origStride, - const pixel* buffOrigin, intptr_t buffStride, - int x, int y, int dx, int dy, - int bs, int besterror, int bitDepth, int errorMode); - void (*applyMotion)(const pixel* pSrcImage, int srcStride, - pixel* pDstImage, int dstStride, - int width, int height, - int blockSizeX, int blockSizeY, - uint32_t mvsStride, const MV* mvs, - int csx, int csy, - int blockRow, int rowSize, int vShift); - void (*lumaBlockAvgVariance)(const pixel* origin, intptr_t stride, - int blockX, int blockY, int blockSize, - double* avgOut, double* varianceOut); + int (*motionErrorLumaFrac)(const pixel* origOrigin, intptr_t origStride, const pixel* buffOrigin, intptr_t buffStride, int x, int y, + int dx, int dy, int bs, int besterror, int bitDepth, int errorMode); + + void (*applyMotion)(const pixel* pSrcImage, int srcStride, pixel* pDstImage, int dstStride, int width, int height, int blockSizeX, + int blockSizeY, uint32_t mvsStride, const MV* mvs, int csx, int csy, int blockRow, int rowSize, int vShift); + + void (*lumaBlockAvgVariance)(const pixel* origin, intptr_t stride, int blockX, int blockY, int blockSize, double* avgOut, double* varianceOut); + + void (*computeBlockStats)(const pixel* srcPel, intptr_t srcStride, const pixel* refPel, intptr_t refStride, int blkSize, int* outVariance, int* outDiffsum); + + void (*bilateralWeightedFilter)(const pixel* srcBlk, intptr_t srcStride, int numRefs, const pixel* const* refBlks, const intptr_t* refStrides, + const double* vww, const double* vsw, double bdw, double maxSample, int blkSize, pixel* dstBlk, intptr_t dstStride); }; +#define MCTF_MAX_REFS 16 extern MCSTFPrimitives mcstfPrim; diff --git a/source/common/x86/temporalfilter_simd.cpp b/source/common/x86/temporalfilter_simd.cpp index 606e3c3f1..91ee2cfe2 100644 --- a/source/common/x86/temporalfilter_simd.cpp +++ b/source/common/x86/temporalfilter_simd.cpp @@ -54,6 +54,31 @@ namespace X265_NS { /* Shared helpers */ + MCSTF_TARGET_AVX2 + static inline __m256d loadPix4_pd(const pixel* p) + { +#if X265_DEPTH > 8 + __m128i v = _mm_loadl_epi64(reinterpret_cast(p)); + return _mm256_cvtepi32_pd(_mm_cvtepu16_epi32(v)); +#else + __m128i v = _mm_cvtsi32_si128(*reinterpret_cast(p)); + return _mm256_cvtepi32_pd(_mm_cvtepu8_epi32(v)); +#endif + } + + MCSTF_TARGET_AVX2 + static inline void storePix4_pd(pixel* p, __m256d v) + { + __m128i i32 = _mm256_cvtpd_epi32(v); +#if X265_DEPTH > 8 + _mm_storel_epi64(reinterpret_cast<__m128i*>(p), + _mm_packus_epi32(i32, i32)); +#else + __m128i u16 = _mm_packus_epi32(i32, i32); + *reinterpret_cast(p) = _mm_cvtsi128_si32(_mm_packus_epi16(u16, u16)); +#endif + } + static inline int hsum_epi32(__m128i v) { __m128i hi = _mm_srli_si128(v, 8); @@ -456,13 +481,7 @@ namespace X265_NS { const int* xFilter = s_interpolationFilter[dx & 0xf]; const int* yFilter = s_interpolationFilter[dy & 0xf]; - applyMotionBlock_AVX2( - pSrcImage, srcStride, - pDstImage, dstStride, - x, y, - blockSizeX, blockSizeY, - xInt, yInt, - xFilter, yFilter); + applyMotionBlock_AVX2( pSrcImage, srcStride, pDstImage, dstStride, x, y, blockSizeX, blockSizeY, xInt, yInt, xFilter, yFilter); } } } @@ -545,6 +564,162 @@ namespace X265_NS { *varianceOut = varLanes[0] + varLanes[1]; } + MCSTF_TARGET_AVX2 + void computeBlockStats_AVX2( const pixel* srcPel, intptr_t srcStride, const pixel* refPel, intptr_t refStride, int blkSize, int* outVariance, int* outDiffsum) + { + if (blkSize == 8) + { + __m128i drow[8]; + for (int y = 0; y < 8; y++) + { +#if X265_DEPTH > 8 + __m128i s = _mm_loadu_si128(reinterpret_cast(srcPel + y * srcStride)); + __m128i r = _mm_loadu_si128(reinterpret_cast(refPel + y * refStride)); +#else + __m128i s = _mm_cvtepu8_epi16(_mm_loadl_epi64(reinterpret_cast(srcPel + y * srcStride))); + __m128i r = _mm_cvtepu8_epi16(_mm_loadl_epi64(reinterpret_cast(refPel + y * refStride))); +#endif + drow[y] = _mm_sub_epi16(s, r); + } + + static const int16_t maskdata[8] = { -1,-1,-1,-1,-1,-1,-1, 0 }; + const __m128i mask7 = _mm_loadu_si128(reinterpret_cast(maskdata)); + + __m256i vVar = _mm256_setzero_si256(); + __m256i vDiff = _mm256_setzero_si256(); + + for (int y = 0; y < 8; y++) + { + __m256i d = _mm256_cvtepi16_epi32(drow[y]); + vVar = _mm256_add_epi32(vVar, _mm256_mullo_epi32(d, d)); + + __m128i hdiff = _mm_and_si128( + _mm_sub_epi16(_mm_srli_si128(drow[y], 2), drow[y]), + mask7); + __m256i hd = _mm256_cvtepi16_epi32(hdiff); + vDiff = _mm256_add_epi32(vDiff, _mm256_mullo_epi32(hd, hd)); + + if (y < 7) + { + __m256i vd = _mm256_cvtepi16_epi32(_mm_sub_epi16(drow[y + 1], drow[y])); + vDiff = _mm256_add_epi32(vDiff, _mm256_mullo_epi32(vd, vd)); + } + } + + *outVariance = hsum_epi32_avx(vVar); + *outDiffsum = hsum_epi32_avx(vDiff); + } + else + { + __m128i drow[4]; + for (int y = 0; y < 4; y++) + { +#if X265_DEPTH > 8 + __m128i s = _mm_loadl_epi64(reinterpret_cast(srcPel + y * srcStride)); + __m128i r = _mm_loadl_epi64(reinterpret_cast(refPel + y * refStride)); +#else + __m128i s = _mm_cvtepu8_epi16(_mm_cvtsi32_si128(*reinterpret_cast(srcPel + y * srcStride))); + __m128i r = _mm_cvtepu8_epi16(_mm_cvtsi32_si128(*reinterpret_cast(refPel + y * refStride))); +#endif + drow[y] = _mm_sub_epi16(s, r); + } + + static const int16_t maskdata3[8] = { -1,-1,-1, 0, 0,0,0,0 }; + const __m128i mask3 = _mm_loadu_si128(reinterpret_cast(maskdata3)); + + __m128i vVar128 = _mm_setzero_si128(); + __m128i vDiff128 = _mm_setzero_si128(); + + for (int y = 0; y < 4; y++) + { + __m128i d = _mm_cvtepi16_epi32(drow[y]); + vVar128 = _mm_add_epi32(vVar128, _mm_mullo_epi32(d, d)); + + __m128i hdiff = _mm_and_si128( + _mm_sub_epi16(_mm_srli_si128(drow[y], 2), drow[y]), + mask3); + __m128i hd = _mm_cvtepi16_epi32(hdiff); + vDiff128 = _mm_add_epi32(vDiff128, _mm_mullo_epi32(hd, hd)); + + if (y < 3) + { + __m128i vd = _mm_cvtepi16_epi32(_mm_sub_epi16(drow[y + 1], drow[y])); + vDiff128 = _mm_add_epi32(vDiff128, _mm_mullo_epi32(vd, vd)); + } + } + + *outVariance = hsum_epi32(vVar128); + *outDiffsum = hsum_epi32(vDiff128); + } + } + + MCSTF_TARGET_AVX2 + void bilateralWeightedFilter_AVX2( + const pixel* srcBlk, intptr_t srcStride, + int numRefs, + const pixel* const* refBlks, + const intptr_t* refStrides, + const double* vww, + const double* vsw, + double bdw, + double maxSample, + int blkSize, + pixel* dstBlk, intptr_t dstStride) + { + const __m256d vBdw = _mm256_set1_pd(bdw); + const __m256d vOne = _mm256_set1_pd(1.0); + const __m256d vZero = _mm256_setzero_pd(); + const __m256d vMax = _mm256_set1_pd(maxSample); + const __m256d vHalf = _mm256_set1_pd(0.5); + + double neg_inv_vsw[MCTF_MAX_REFS]; + for (int i = 0; i < numRefs; i++) + neg_inv_vsw[i] = -1.0 / vsw[i]; + + ALIGN_VAR_32(double, expArgs[4]); + ALIGN_VAR_32(double, expVals[4]); + + for (int y = 0; y < blkSize; y++) + { + for (int xg = 0; xg < blkSize; xg += 4) + { + __m256d vOrgVal = loadPix4_pd(srcBlk + y * srcStride + xg); + __m256d vNewVal = vOrgVal; + __m256d vWeightSum = vOne; + + for (int i = 0; i < numRefs; i++) + { + __m256d vRefVal = loadPix4_pd(refBlks[i] + y * refStrides[i] + xg); + + __m256d vDiff = _mm256_mul_pd(_mm256_sub_pd(vRefVal, vOrgVal), vBdw); + __m256d vDiffSq = _mm256_mul_pd(vDiff, vDiff); + + __m256d vExpArg = _mm256_mul_pd(vDiffSq, _mm256_set1_pd(neg_inv_vsw[i])); + + _mm256_store_pd(expArgs, vExpArg); + expVals[0] = exp(expArgs[0]); + expVals[1] = exp(expArgs[1]); + expVals[2] = exp(expArgs[2]); + expVals[3] = exp(expArgs[3]); + __m256d vExpResult = _mm256_load_pd(expVals); + + __m256d vWeight = _mm256_mul_pd(_mm256_set1_pd(vww[i]), vExpResult); + + vNewVal = _mm256_add_pd(vNewVal, _mm256_mul_pd(vWeight, vRefVal)); + vWeightSum = _mm256_add_pd(vWeightSum, vWeight); + } + + vNewVal = _mm256_div_pd(vNewVal, vWeightSum); + + vNewVal = _mm256_floor_pd(_mm256_add_pd(vNewVal, vHalf)); + + vNewVal = _mm256_max_pd(vZero, _mm256_min_pd(vMax, vNewVal)); + + storePix4_pd(dstBlk + y * dstStride + xg, vNewVal); + } + } + } + /* Dispatch - overrides scalar defaults with SIMD variants at runtime */ void setupMCTFPrimitives_x86(MCSTFPrimitives & p, int cpuMask) { @@ -553,6 +728,8 @@ namespace X265_NS { p.motionErrorLumaFrac = motionErrorLumaFrac_AVX2; p.applyMotion = applyMotion_AVX2; p.lumaBlockAvgVariance = lumaBlockAvgVariance_AVX2; + p.computeBlockStats = computeBlockStats_AVX2; + p.bilateralWeightedFilter = bilateralWeightedFilter_AVX2; } } } /* namespace X265_NS */ From 5914a1af6782aacfe93355e9507733b05d919322 Mon Sep 17 00:00:00 2001 From: Logaprakash Ramajayam Date: Mon, 29 Jun 2026 13:51:14 +0530 Subject: [PATCH 13/23] Enable profiling stats for MCSTF --- source/encoder/encoder.cpp | 16 ++++++++++----- source/encoder/slicetype.cpp | 38 ++++++++++++++++++++++++------------ source/encoder/slicetype.h | 16 ++++++++------- 3 files changed, 45 insertions(+), 25 deletions(-) diff --git a/source/encoder/encoder.cpp b/source/encoder/encoder.cpp index adfff307c..8f85244d1 100644 --- a/source/encoder/encoder.cpp +++ b/source/encoder/encoder.cpp @@ -2924,11 +2924,10 @@ void Encoder::printSummary() for (int i = 0; i < m_numPools; i++) totalWorkerCount += m_threadPool[i].m_numWorkers; - int64_t batchElapsedTime, coopSliceElapsedTime; - uint64_t batchCount, coopSliceCount; - m_lookahead->getWorkerStats(batchElapsedTime, batchCount, coopSliceElapsedTime, coopSliceCount); + int64_t framecostBatchElapsedTime, coopSliceElapsedTime, mcstfBatchElapsedTime; + m_lookahead->getWorkerStats(framecostBatchElapsedTime, coopSliceElapsedTime, mcstfBatchElapsedTime); int64_t lookaheadWorkerTime = m_lookahead->m_slicetypeDecideElapsedTime + m_lookahead->m_preLookaheadElapsedTime + - batchElapsedTime + coopSliceElapsedTime; + framecostBatchElapsedTime + coopSliceElapsedTime + mcstfBatchElapsedTime; int64_t totalWorkerTime = cuStats.totalCTUTime + cuStats.loopFilterElapsedTime + cuStats.pmodeTime + cuStats.pmeTime + lookaheadWorkerTime + cuStats.weightAnalyzeTime + cuStats.tmeTime; @@ -3015,10 +3014,17 @@ void Encoder::printSummary() ELAPSED_MSEC(cuStats.pmodeTime) / cuStats.countPModeTasks); } - x265_log(m_param, X265_LOG_INFO, "CU: %%%05.2lf time spent in slicetypeDecide (avg %.3lfms) and prelookahead (avg %.3lfms)\n", + x265_log(m_param, X265_LOG_INFO, "CU: %%%05.2lf time spent in slicetypeDecide (avg %.3lfms), framecost estimation (avg %.3lfms) and prelookahead (avg %.3lfms)\n", 100.0 * lookaheadWorkerTime / totalWorkerTime, ELAPSED_MSEC(m_lookahead->m_slicetypeDecideElapsedTime) / m_lookahead->m_countSlicetypeDecide, + ELAPSED_MSEC(m_lookahead->m_framecostElapsedTime) / m_lookahead->m_countFramecosts, ELAPSED_MSEC(m_lookahead->m_preLookaheadElapsedTime) / m_lookahead->m_countPreLookahead); + if (m_param->bEnableTemporalFilter) + { + x265_log(m_param, X265_LOG_INFO, "CU: %%%05.2lf time spent in temporal filtering (avg %.3lfms per frame)\n", + 100.0 * mcstfBatchElapsedTime / totalWorkerTime, + ELAPSED_MSEC(m_lookahead->m_temporalFilterElapsedTime) / m_lookahead->m_countTemporalFilter); + } x265_log(m_param, X265_LOG_INFO, "CU: %%%05.2lf time spent in other tasks\n", 100.0 * unaccounted / totalWorkerTime); diff --git a/source/encoder/slicetype.cpp b/source/encoder/slicetype.cpp index 39ad0d7f0..42b171bf6 100644 --- a/source/encoder/slicetype.cpp +++ b/source/encoder/slicetype.cpp @@ -36,9 +36,11 @@ #include "ratecontrol.h" #if DETAILED_CU_STATS -#define ProfileLookaheadTime(elapsed, count) ScopedElapsedTime _scope(elapsed); count++ +#define ProfileLookaheadTimeCount(elapsed, count) ScopedElapsedTime _scope(elapsed); count++ +#define ProfileLookaheadTime(elapsed) ScopedElapsedTime _scope(elapsed) #else -#define ProfileLookaheadTime(elapsed, count) +#define ProfileLookaheadTimeCount(elapsed, count) +#define ProfileLookaheadTime(elapsed) #endif using namespace X265_NS; @@ -1055,8 +1057,12 @@ Lookahead::Lookahead(x265_param *param, ThreadPool* pool) #if DETAILED_CU_STATS m_slicetypeDecideElapsedTime = 0; m_preLookaheadElapsedTime = 0; + m_framecostElapsedTime = 0; + m_temporalFilterElapsedTime = 0; m_countSlicetypeDecide = 0; m_countPreLookahead = 0; + m_countFramecosts = 0; + m_countTemporalFilter = 0; #endif m_accHistDiffRunningAvgCb = X265_MALLOC(uint32_t*, NUMBER_OF_SEGMENTS_IN_WIDTH * sizeof(uint32_t*)); @@ -1104,17 +1110,17 @@ Lookahead::Lookahead(x265_param *param, ThreadPool* pool) } #if DETAILED_CU_STATS -void Lookahead::getWorkerStats(int64_t& batchElapsedTime, uint64_t& batchCount, int64_t& coopSliceElapsedTime, uint64_t& coopSliceCount) +void Lookahead::getWorkerStats(int64_t& framecostBatchElapsedTime, int64_t& coopSliceElapsedTime, int64_t& mcstfBatchElapsedTime) { - batchElapsedTime = coopSliceElapsedTime = 0; - coopSliceCount = batchCount = 0; + framecostBatchElapsedTime = 0; + coopSliceElapsedTime = 0; + mcstfBatchElapsedTime = 0; int tldCount = m_pool ? m_pool->m_numWorkers : 1; for (int i = 0; i < tldCount; i++) { - batchElapsedTime += m_tld[i].batchElapsedTime; + framecostBatchElapsedTime += m_tld[i].framecostBatchElapsedTime; coopSliceElapsedTime += m_tld[i].coopSliceElapsedTime; - batchCount += m_tld[i].countBatches; - coopSliceCount += m_tld[i].countCoopSlices; + mcstfBatchElapsedTime += m_tld[i].mcstfBatchElapsedTime; } } #endif @@ -1283,7 +1289,7 @@ void Lookahead::findJob(int /*workerThreadID*/) if (!doDecide) return; - ProfileLookaheadTime(m_slicetypeDecideElapsedTime, m_countSlicetypeDecide); + ProfileLookaheadTimeCount(m_slicetypeDecideElapsedTime, m_countSlicetypeDecide); ProfileScopeEvent(slicetypeDecideEV); slicetypeDecide(); @@ -1752,7 +1758,6 @@ void PreLookaheadGroup::processTasks(int workerThreadID) while (m_jobAcquired < m_jobTotal) { Frame* preFrame = m_preframes[m_jobAcquired++]; - ProfileLookaheadTime(m_lookahead.m_preLookaheadElapsedTime, m_lookahead.m_countPreLookahead); ProfileScopeEvent(prelookahead); m_lock.release(); preFrame->m_lowres.init(preFrame->m_fencPic, preFrame->m_poc, m_lookahead.m_param->bEnableTemporalFilter); @@ -1942,6 +1947,7 @@ void Lookahead::slicetypeDecide() /* perform pre-analysis on frames which need it, using a bonded task group */ if (pre.m_jobTotal) { + ProfileLookaheadTimeCount(m_preLookaheadElapsedTime, m_countPreLookahead); if (m_pool) pre.tryBondPeers(*m_pool, pre.m_jobTotal); pre.processTasks(-1); @@ -2162,6 +2168,8 @@ void Lookahead::slicetypeDecide() fflush(stderr); } + ProfileLookaheadTimeCount(m_temporalFilterElapsedTime, m_countTemporalFilter); + const int rowMELevels = 4; const int rowSize = 16; const int origHeight = frameEnc->m_fencPic->m_picHeight; @@ -2812,6 +2820,7 @@ void Lookahead::slicetypeAnalyse(Lowres **frames, bool bKeyframe) if (m_bBatchMotionSearch) { + ProfileLookaheadTimeCount(m_framecostElapsedTime, m_countFramecosts); /* pre-calculate all motion searches, using many worker threads */ CostEstimateGroup estGroup(*this, frames); for (int b = 2; b < numFrames; b++) @@ -4104,13 +4113,13 @@ void CostEstimateGroup::processTasks(int workerThreadID) if (m_batchMode) { - ProfileLookaheadTime(tld.batchElapsedTime, tld.countBatches); ProfileScopeEvent(estCostSingle); - Estimate& e = m_estimates[i]; Frame* curFrame = e.frame; if (m_lookahead.m_param->bEnableTemporalFilter && curFrame && (curFrame->m_lowres.sliceType == X265_TYPE_IDR || curFrame->m_lowres.sliceType == X265_TYPE_I || curFrame->m_lowres.sliceType == X265_TYPE_P)) { + ProfileLookaheadTime(tld.mcstfBatchElapsedTime); + m_metld.m_bitDepth = curFrame->m_param->internalBitDepth; TemporalFilterRefPicInfo* ref = &curFrame->m_mcstfRefList[e.p0]; @@ -4128,11 +4137,14 @@ void CostEstimateGroup::processTasks(int workerThreadID) } } else + { + ProfileLookaheadTime(tld.framecostBatchElapsedTime); estimateFrameCost(tld, e.p0, e.p1, e.b, false); + } } else { - ProfileLookaheadTime(tld.coopSliceElapsedTime, tld.countCoopSlices); + ProfileLookaheadTime(tld.coopSliceElapsedTime); ProfileScopeEvent(estCostCoop); X265_CHECK(i < MAX_COOP_SLICES, "impossible number of coop slices\n"); diff --git a/source/encoder/slicetype.h b/source/encoder/slicetype.h index 52395a6d0..2e6d565e1 100644 --- a/source/encoder/slicetype.h +++ b/source/encoder/slicetype.h @@ -81,10 +81,9 @@ struct LookaheadTLD int paddedLines; #if DETAILED_CU_STATS - int64_t batchElapsedTime; + int64_t framecostBatchElapsedTime; int64_t coopSliceElapsedTime; - uint64_t countBatches; - uint64_t countCoopSlices; + int64_t mcstfBatchElapsedTime; #endif LookaheadTLD() @@ -96,10 +95,9 @@ struct LookaheadTLD widthInCU = heightInCU = ncu = paddedLines = 0; #if DETAILED_CU_STATS - batchElapsedTime = 0; + framecostBatchElapsedTime = 0; coopSliceElapsedTime = 0; - countBatches = 0; - countCoopSlices = 0; + mcstfBatchElapsedTime = 0; #endif } @@ -210,9 +208,13 @@ class Lookahead : public JobProvider #if DETAILED_CU_STATS int64_t m_slicetypeDecideElapsedTime; int64_t m_preLookaheadElapsedTime; + int64_t m_framecostElapsedTime; + int64_t m_temporalFilterElapsedTime; uint64_t m_countSlicetypeDecide; uint64_t m_countPreLookahead; - void getWorkerStats(int64_t& batchElapsedTime, uint64_t& batchCount, int64_t& coopSliceElapsedTime, uint64_t& coopSliceCount); + uint64_t m_countFramecosts; + uint64_t m_countTemporalFilter; + void getWorkerStats(int64_t& framecostBatchElapsedTime, int64_t& coopSliceElapsedTime, int64_t& mcstfBatchElapsedTime); #endif bool create(); From 4ca99dd4da8c8edbe2a0ed77eed50de62813ce23 Mon Sep 17 00:00:00 2001 From: Logaprakash Ramajayam Date: Wed, 1 Jul 2026 10:00:18 +0530 Subject: [PATCH 14/23] Fix build warnings --- doc/reST/cli.rst | 8 ++++++++ source/common/param.cpp | 5 +++++ source/common/temporalfilter.cpp | 33 ++++++++++++-------------------- source/common/temporalfilter.h | 6 +++--- source/encoder/slicetype.cpp | 14 +++++++------- 5 files changed, 35 insertions(+), 31 deletions(-) diff --git a/doc/reST/cli.rst b/doc/reST/cli.rst index 31304fe90..4e24e01ea 100755 --- a/doc/reST/cli.rst +++ b/doc/reST/cli.rst @@ -1368,6 +1368,14 @@ Temporal / motion search options Note : MCSTF should be enabled only with frame threads 1 +.. option:: --mcstf-ref-range + + Number of reference frames to consider on each side (past and future) of the + current frame during Motion-compensated spatio-temporal filtering. A higher value + includes more temporal neighbors for filtering, which can improve noise reduction + at the cost of increased memory usage and processing time. The valid range is 1 to 4. + Default: 2 + Spatial/intra options ===================== diff --git a/source/common/param.cpp b/source/common/param.cpp index 244e4a06d..fd6d33ab6 100755 --- a/source/common/param.cpp +++ b/source/common/param.cpp @@ -1960,6 +1960,11 @@ int x265_check_params(x265_param* param) } } } + if (param->mcstfFrameRange > 8) + { + param->mcstfFrameRange = 8; + x265_log(param, X265_LOG_WARNING, "MCSTF reference range should not exceed 8. Setting MCSTF reference range to 8\n"); + } if (param->bEnableHME) { for (int level = 0; level < 3; level++) diff --git a/source/common/temporalfilter.cpp b/source/common/temporalfilter.cpp index 32c0836fe..626ee6d11 100644 --- a/source/common/temporalfilter.cpp +++ b/source/common/temporalfilter.cpp @@ -105,14 +105,12 @@ namespace X265_NS { static const int centreTapOffset = 3; const int maxValue = (1 << X265_DEPTH) - 1; - const int blkRowStart = (blockRow * rowSize) >> vShift; - const int blkRowEnd = X265_MIN((blockRow * rowSize + rowSize) >> vShift, height); - const int rowStart = (!rowSize) ? 0 : blkRowStart; - const int rowEnd = (!rowSize) ? height : blkRowEnd; - int blockNumY = (!rowSize) ? 0 : blkRowStart / blockSizeY; - - for (int y = rowStart; - y + blockSizeY <= rowEnd; + const int blkRowStart = (!rowSize) ? 0 : (blockRow * rowSize) >> vShift; + const int blkRowEnd = (!rowSize) ? height : X265_MIN((blockRow * rowSize + rowSize) >> vShift, height); + int blockNumY = (!rowSize) ? 0 : blkRowStart / blockSizeY; + + for (int y = blkRowStart; + y + blockSizeY <= blkRowEnd; y += blockSizeY, blockNumY++) { for (int x = 0, blockNumX = 0; @@ -549,7 +547,6 @@ void TemporalFilter::applyMotion(MV *mvs, uint32_t mvsStride, PicYuv *input, Pic int csx = 0, csy = 0; for (int c = 0; c < m_numComponents; c++) { - const pixel maxValue = (1 << X265_DEPTH) - 1; const pixel* pSrcImage = input->m_picOrg[c]; pixel* pDstImage = output->m_picOrg[c]; @@ -572,18 +569,12 @@ void TemporalFilter::applyMotion(MV *mvs, uint32_t mvsStride, PicYuv *input, Pic const int width = input->m_picWidth >> csx; const int vShift = (!c) ? 0 : csy; - const int blkRowStart = (blockRow * rowSize) >> vShift; - const int blkRowEnd = X265_MIN((blockRow * rowSize + rowSize) >> vShift, height); - - const int rowStart = (!rowSize) ? 0 : blkRowStart; - const int rowEnd = (!rowSize) ? height : blkRowEnd; - int blockNumY = (!rowSize) ? 0 : blkRowStart / blockSizeY; mcstfPrim.applyMotion(pSrcImage, srcStride, pDstImage, dstStride, width, height, blockSizeX, blockSizeY, mvsStride, mvs, csx, csy, blockRow, rowSize, vShift); } } -void TemporalFilter::bilateralFilter_core(Frame* frame, TemporalFilterRefPicInfo* m_mcstfRefList, int numRefs, int blockRow, int rowSize, double overallStrength) +void TemporalFilter::bilateralFilterCore(Frame* frame, TemporalFilterRefPicInfo* m_mcstfRefList, int numRefs, int blockRow, int rowSize, double overallStrength) { int refStrengthRow = 0; @@ -614,8 +605,8 @@ void TemporalFilter::bilateralFilter_core(Frame* frame, TemporalFilterRefPicInfo const int blkSize = (!c) ? 8 : 4; const int vShift = (!c) ? 0 : csy; - const int planeRowStart = (blockRow * rowSize) >> vShift; - const int planeRowEnd = X265_MIN((blockRow * rowSize + rowSize) >> vShift, height); + const int planeRowStart = (!rowSize) ? 0 : (blockRow * rowSize) >> vShift; + const int planeRowEnd = (!rowSize) ? height : X265_MIN((blockRow * rowSize + rowSize) >> vShift, height); const int blkRowStart = (planeRowStart / blkSize) * blkSize; const int blkRowEnd = X265_MIN(((planeRowEnd + blkSize - 1) / blkSize) * blkSize, height); @@ -626,7 +617,7 @@ void TemporalFilter::bilateralFilter_core(Frame* frame, TemporalFilterRefPicInfo const pixel* srcPel = srcPelPlane + by * srcStride + bx; // Step 1: noise computation via SIMD primitive - double minError = 9999999; + double minError = INT64_MAX; for (int i = 0; i < numRefs; i++) { TemporalFilterRefPicInfo* refPicInfo = &m_mcstfRefList[i]; @@ -698,7 +689,7 @@ void TemporalFilter::bilateralFilter(Frame* curFrame, TemporalFilterRefPicInfo* if (!pool) { - bilateralFilter_core(curFrame, mctfRefList, numRef, 0, 0, overallStrength); + bilateralFilterCore(curFrame, mctfRefList, numRef, 0, 0, overallStrength); return; } @@ -832,7 +823,7 @@ void MotionEstimatorTLD::motionEstimationLumaDoubleRes(MotionEstimatorTLD& m_met const int origHeight = orig->m_picHeight; int rowStart = row * rowSize; - if (row * rowSize > origHeight) + if (rowStart > origHeight) return; // row beyond frame edge — nothing to do int rowEnd = (!rowSize) ? origHeight : X265_MIN(rowStart + rowSize, origHeight); diff --git a/source/common/temporalfilter.h b/source/common/temporalfilter.h index 584abd026..34becb04b 100644 --- a/source/common/temporalfilter.h +++ b/source/common/temporalfilter.h @@ -225,7 +225,7 @@ namespace X265_NS { double overallStrength, ThreadPool* pool); - void bilateralFilter_core(Frame* frame, + void bilateralFilterCore(Frame* frame, TemporalFilterRefPicInfo* mctfRefList, int numRef, int blockRow, @@ -286,7 +286,7 @@ namespace X265_NS { m_jobTotal = m_jobAcquired = 0; } - void processTasks(int /*workerThreadID*/) override + void processTasks(int /*workerThreadID*/) { m_lock.acquire(); while (m_jobAcquired < m_jobTotal) @@ -295,7 +295,7 @@ namespace X265_NS { m_lock.release(); const FilterJob& j = m_jobs[i]; - m_filter.bilateralFilter_core(j.frame, j.mctfRefList, j.numRef, + m_filter.bilateralFilterCore(j.frame, j.mctfRefList, j.numRef, j.blockRow, j.rowSize, j.overallStrength); m_lock.acquire(); diff --git a/source/encoder/slicetype.cpp b/source/encoder/slicetype.cpp index 42b171bf6..92b5923f8 100644 --- a/source/encoder/slicetype.cpp +++ b/source/encoder/slicetype.cpp @@ -2160,7 +2160,7 @@ void Lookahead::slicetypeDecide() Frame* frameEnc = m_inputQueue.first(); for (int b = 0; b < m_inputQueue.size(); b++) { - if (m_param->bEnableTemporalFilter && isFilterThisframe(frameEnc->m_mcstf->m_sliceTypeConfig, frameEnc->m_lowres.sliceType)) + if (isFilterThisframe(frameEnc->m_mcstf->m_sliceTypeConfig, frameEnc->m_lowres.sliceType)) { if (!generatemcstf(frameEnc, m_origPicBuf->m_mcstfPicList, m_inputQueue.last()->m_poc)) { @@ -4122,17 +4122,17 @@ void CostEstimateGroup::processTasks(int workerThreadID) m_metld.m_bitDepth = curFrame->m_param->internalBitDepth; TemporalFilterRefPicInfo* ref = &curFrame->m_mcstfRefList[e.p0]; - + int rowSize = 16; if (e.MElevel == 0) - m_metld.motionEstimationLuma(m_metld, ref->mvs0, ref->mvsStride0, curFrame->m_lowres.lowerResPlane[0], (int)(curFrame->m_lowres.lumaStride / 2), (curFrame->m_lowres.lines / 2), (curFrame->m_lowres.width / 2), ref->lowerRes, 16, curFrame->m_param->searchRangeForLayer2, e.blockRow, 16); + m_metld.motionEstimationLuma(m_metld, ref->mvs0, ref->mvsStride0, curFrame->m_lowres.lowerResPlane[0], (int)(curFrame->m_lowres.lumaStride / 2), (curFrame->m_lowres.lines / 2), (curFrame->m_lowres.width / 2), ref->lowerRes, 16, curFrame->m_param->searchRangeForLayer2, e.blockRow, rowSize); else if (e.MElevel == 1) - m_metld.motionEstimationLuma(m_metld, ref->mvs1, ref->mvsStride1, curFrame->m_lowres.lowresPlane[0], (int)(curFrame->m_lowres.lumaStride), (curFrame->m_lowres.lines), (curFrame->m_lowres.width), ref->lowres, 16, curFrame->m_param->searchRangeForLayer1, e.blockRow, 16, ref->mvs0, ref->mvsStride0, 2); + m_metld.motionEstimationLuma(m_metld, ref->mvs1, ref->mvsStride1, curFrame->m_lowres.lowresPlane[0], (int)(curFrame->m_lowres.lumaStride), (curFrame->m_lowres.lines), (curFrame->m_lowres.width), ref->lowres, 16, curFrame->m_param->searchRangeForLayer1, e.blockRow, rowSize, ref->mvs0, ref->mvsStride0, 2); else if (e.MElevel == 2) - m_metld.motionEstimationLuma(m_metld, ref->mvs2, ref->mvsStride2, curFrame->m_fencPic->m_picOrg[0], (int)curFrame->m_fencPic->m_stride, curFrame->m_fencPic->m_picHeight, curFrame->m_fencPic->m_picWidth, ref->picBuffer->m_picOrg[0], 16, curFrame->m_param->searchRangeForLayer0, e.blockRow, 16, ref->mvs1, ref->mvsStride1, 2); + m_metld.motionEstimationLuma(m_metld, ref->mvs2, ref->mvsStride2, curFrame->m_fencPic->m_picOrg[0], (int)curFrame->m_fencPic->m_stride, curFrame->m_fencPic->m_picHeight, curFrame->m_fencPic->m_picWidth, ref->picBuffer->m_picOrg[0], 16, curFrame->m_param->searchRangeForLayer0, e.blockRow, rowSize, ref->mvs1, ref->mvsStride1, 2); else if (e.MElevel == 3) { - m_metld.motionEstimationLumaDoubleRes(m_metld, ref->mvs, ref->mvsStride, curFrame->m_fencPic, ref->picBuffer, 8, ref->mvs2, ref->mvsStride2, 1, ref->error, e.blockRow, 16); - if (e.blockRow == (int)(curFrame->m_fencPic->m_picHeight - 15)/16 - 1) + m_metld.motionEstimationLumaDoubleRes(m_metld, ref->mvs, ref->mvsStride, curFrame->m_fencPic, ref->picBuffer, 8, ref->mvs2, ref->mvsStride2, 1, ref->error, e.blockRow, rowSize); + if (e.blockRow == (int)(curFrame->m_fencPic->m_picHeight + (rowSize - 1))/rowSize - 1) curFrame->m_lowres.lowresMcstfMvs[0][e.p0][0].x = 1; } } From 4b00e46f5b7b5ab507fda057ac29e04ed0306ef2 Mon Sep 17 00:00:00 2001 From: gunasrij Date: Fri, 17 Jul 2026 19:11:18 +0530 Subject: [PATCH 15/23] mcstf: Move AVX2 implementation to common/vec --- source/common/CMakeLists.txt | 10 +- source/common/primitives.cpp | 7 +- source/common/temporalfilter.cpp | 134 ++++++++---------- source/common/temporalfilter.h | 35 ++--- .../temporalfilter-avx2.cpp} | 95 +++++++------ source/common/vec/vec-primitives.cpp | 6 + source/x265.h | 2 +- 7 files changed, 133 insertions(+), 156 deletions(-) rename source/common/{x86/temporalfilter_simd.cpp => vec/temporalfilter-avx2.cpp} (91%) diff --git a/source/common/CMakeLists.txt b/source/common/CMakeLists.txt index 64fdf0a91..c920c4c7a 100644 --- a/source/common/CMakeLists.txt +++ b/source/common/CMakeLists.txt @@ -22,9 +22,10 @@ if(ENABLE_ASSEMBLY AND X86) set(SSE3 vec/dct-sse3.cpp) set(SSSE3 vec/dct-ssse3.cpp) set(SSE41 vec/dct-sse41.cpp) + set(AVX2 vec/temporalfilter-avx2.cpp) if(MSVC) - set(PRIMITIVES ${SSE3} ${SSSE3} ${SSE41}) + set(PRIMITIVES ${SSE3} ${SSSE3} ${SSE41} ${AVX2}) set(WARNDISABLE "/wd4100") # unreferenced formal parameter if(INTEL_CXX) add_definitions(/Qwd111) # statement is unreachable @@ -39,7 +40,7 @@ if(ENABLE_ASSEMBLY AND X86) # x64 implies SSE4, so only add /arch:SSE2 if building for Win32 set_source_files_properties(${SSE3} ${SSSE3} ${SSE41} PROPERTIES COMPILE_FLAGS "${WARNDISABLE} /arch:SSE2") endif() - set_source_files_properties(x86/temporalfilter_simd.cpp PROPERTIES COMPILE_FLAGS "${WARNDISABLE} /arch:AVX2") + set_source_files_properties(${AVX2} PROPERTIES COMPILE_FLAGS "${WARNDISABLE} /arch:AVX2") endif() if(GCC) if(CLANG) @@ -49,15 +50,16 @@ if(ENABLE_ASSEMBLY AND X86) set(WARNDISABLE "-Wno-unused-parameter") endif() if(INTEL_CXX OR CLANG OR (NOT CC_VERSION VERSION_LESS 4.3)) - set(PRIMITIVES ${SSE3} ${SSSE3} ${SSE41}) + set(PRIMITIVES ${SSE3} ${SSSE3} ${SSE41} ${AVX2}) set_source_files_properties(${SSE3} PROPERTIES COMPILE_FLAGS "${WARNDISABLE} -msse3") set_source_files_properties(${SSSE3} PROPERTIES COMPILE_FLAGS "${WARNDISABLE} -mssse3") set_source_files_properties(${SSE41} PROPERTIES COMPILE_FLAGS "${WARNDISABLE} -msse4.1") + set_source_files_properties(${AVX2} PROPERTIES COMPILE_FLAGS "${WARNDISABLE} -mavx2") endif() endif() set(VEC_PRIMITIVES vec/vec-primitives.cpp ${PRIMITIVES}) source_group(Intrinsics FILES ${VEC_PRIMITIVES}) - set(C_SRCS asm-primitives.cpp pixel.h mc.h ipfilter8.h blockcopy8.h dct8.h loopfilter.h seaintegral.h temporalfilter_simd.cpp) + set(C_SRCS asm-primitives.cpp pixel.h mc.h ipfilter8.h blockcopy8.h dct8.h loopfilter.h seaintegral.h) set(A_SRCS pixel-a.asm const-a.asm cpu-a.asm ssd-a.asm mc-a.asm mc-a2.asm pixel-util8.asm blockcopy8.asm pixeladd8.asm dct8.asm seaintegral.asm) diff --git a/source/common/primitives.cpp b/source/common/primitives.cpp index 927529130..427fbc1df 100644 --- a/source/common/primitives.cpp +++ b/source/common/primitives.cpp @@ -254,11 +254,8 @@ void x265_setup_primitives(x265_param *param) { setupCPrimitives(primitives); - /* MCTF primitives: scalar defaults, then x86 SIMD overrides on capable CPUs */ - setupMCTFPrimitives_scalar(mcstfPrim); -#if ENABLE_ASSEMBLY && X265_ARCH_X86 - setupMCTFPrimitives_x86(mcstfPrim, param->cpuid); -#endif + /* MCSTF primitives: scalar defaults, then x86 SIMD overrides on capable CPUs */ + setupMCSTFPrimitives_scalar(mcstfPrim); /* We do not want the encoder to use the un-optimized intra all-angles * C references. It is better to call the individual angle functions diff --git a/source/common/temporalfilter.cpp b/source/common/temporalfilter.cpp index 626ee6d11..0260ff869 100644 --- a/source/common/temporalfilter.cpp +++ b/source/common/temporalfilter.cpp @@ -39,7 +39,7 @@ namespace X265_NS { const pixel* origOrigin, intptr_t origStride, const pixel* buffOrigin, intptr_t buffStride, int x, int y, int dx, int dy, - int bs, int besterror, int bitDepth, int errorMode) + int bs, int besterror, int bitDepth) { const int* xFilter = s_interpolationFilter[dx & 0xF]; const int* yFilter = s_interpolationFilter[dy & 0xF]; @@ -82,10 +82,7 @@ namespace X265_NS { iSum = iSum < 0 ? 0 : (iSum > maxSampleValue ? maxSampleValue : iSum); int diff = iSum - origRow[x + x1]; - if (errorMode == 0) - error += abs(diff); - else - error += diff * diff; + error += diff * diff; } if (error > besterror) return error; @@ -179,6 +176,49 @@ namespace X265_NS { } } + static void lumaBlockAvgVariance_c( + const pixel* origin, + intptr_t stride, + int blockX, + int blockY, + int blockSize, + double* avgOut, + double* varianceOut) + { + const pixel* base = origin + blockY * stride + blockX; + + int64_t totalSum = 0; + + // Pass 1: Compute sum + for (int y = 0; y < blockSize; y++) + { + const pixel* row = base + y * stride; + + for (int x = 0; x < blockSize; x++) + totalSum += row[x]; + } + + const int N = blockSize * blockSize; + const double avg = (double)totalSum / N; + *avgOut = avg; + + // Pass 2: Compute sum of squared differences + double variance = 0.0; + + for (int y = 0; y < blockSize; y++) + { + const pixel* row = base + y * stride; + + for (int x = 0; x < blockSize; x++) + { + double diff = (double)row[x] - avg; + variance += diff * diff; + } + } + + *varianceOut = variance; + } + static void computeBlockStats_c( const pixel* srcPel, intptr_t srcStride, const pixel* refPel, intptr_t refStride, @@ -210,7 +250,7 @@ namespace X265_NS { *outDiffsum = diffsum; } - static void bilateralWeightedFilter_c( + static void bilateralFilter_c( const pixel* srcBlk, intptr_t srcStride, int numRefs, const pixel* const* refBlks, @@ -249,12 +289,13 @@ namespace X265_NS { /* Global MCSTF primitives table */ MCSTFPrimitives mcstfPrim; - void setupMCTFPrimitives_scalar(MCSTFPrimitives& p) + void setupMCSTFPrimitives_scalar(MCSTFPrimitives& p) { p.motionErrorLumaFrac = motionErrorLumaFrac_c; p.applyMotion = applyMotion_c; + p.lumaBlockAvgVariance = lumaBlockAvgVariance_c; p.computeBlockStats = computeBlockStats_c; - p.bilateralWeightedFilter = bilateralWeightedFilter_c; + p.bilateralFilter = bilateralFilter_c; } }/* namespace X265_NS */ @@ -420,65 +461,6 @@ int TemporalFilter::createRefPicInfo(TemporalFilterRefPicInfo* refFrame, x265_pa return 0; } -int MotionEstimatorTLD::motionErrorLumaSAD(MotionEstimatorTLD& m_metld, - pixel* src, - int stride, - pixel* buf, - int x, - int y, - int dx, - int dy, - int bs, - int besterror) -{ - - pixel* origOrigin = src; - intptr_t origStride = stride; - pixel *buffOrigin = buf; - intptr_t buffStride = stride; - int error = 0;// dx * 10 + dy * 10; - if (((dx | dy) & 0xF) == 0) - { - dx /= m_motionVectorFactor; - dy /= m_motionVectorFactor; - - const pixel* bufferRowStart = buffOrigin + (y + dy) * buffStride + (x + dx); -#if 0 - const pixel* origRowStart = origOrigin + y *origStride + x; - - for (int y1 = 0; y1 < bs; y1++) - { - for (int x1 = 0; x1 < bs; x1++) - { - int diff = origRowStart[x1] - bufferRowStart[x1]; - error += abs(diff); - } - - origRowStart += origStride; - bufferRowStart += buffStride; - } -#else - int partEnum = partitionFromSizes(bs, bs); - /* copy PU block into cache */ - primitives.pu[partEnum].copy_pp(predPUYuv.m_buf[0], FENC_STRIDE, bufferRowStart, buffStride); - - error = m_metld.me.bufSAD(predPUYuv.m_buf[0], FENC_STRIDE); -#endif - if (error > besterror) - { - return error; - } - } - else - { - error = mcstfPrim.motionErrorLumaFrac( - origOrigin, origStride, buffOrigin, buffStride, - x, y, dx, dy, bs, besterror, m_bitDepth, 0 /*SAD*/); - if (error > besterror) return error; - } - return error; -} - int MotionEstimatorTLD::motionErrorLumaSSD(MotionEstimatorTLD& m_metld, pixel* src, int stride, @@ -533,7 +515,7 @@ int MotionEstimatorTLD::motionErrorLumaSSD(MotionEstimatorTLD& m_metld, { error = mcstfPrim.motionErrorLumaFrac( origOrigin, origStride, buffOrigin, buffStride, - x, y, dx, dy, bs, besterror, m_bitDepth, 1 /*SAD*/); + x, y, dx, dy, bs, besterror, m_bitDepth); if (error > besterror) return error; } return error; @@ -640,8 +622,8 @@ void TemporalFilter::bilateralFilterCore(Frame* frame, TemporalFilterRefPicInfo* } // Step 2: pre-compute vww / vsw (block-level) - double vww[MCTF_MAX_REFS] = {}; - double vsw[MCTF_MAX_REFS] = {}; + double vww[MCSTF_MAX_REFS] = {}; + double vsw[MCSTF_MAX_REFS] = {}; for (int i = 0; i < numRefs; i++) { TemporalFilterRefPicInfo* refPicInfo = &m_mcstfRefList[i]; @@ -659,8 +641,8 @@ void TemporalFilter::bilateralFilterCore(Frame* frame, TemporalFilterRefPicInfo* } // Step 3: pixel filtering via SIMD primitive - const pixel* refBlkPtrs[MCTF_MAX_REFS]; - intptr_t refBlkStrides[MCTF_MAX_REFS]; + const pixel* refBlkPtrs[MCSTF_MAX_REFS]; + intptr_t refBlkStrides[MCSTF_MAX_REFS]; for (int i = 0; i < numRefs; i++) { TemporalFilterRefPicInfo* refPicInfo = &m_mcstfRefList[i]; @@ -669,7 +651,7 @@ void TemporalFilter::bilateralFilterCore(Frame* frame, TemporalFilterRefPicInfo* refBlkPtrs[i] = refPicInfo->compensatedPic->m_picOrg[c] + by * refBlkStrides[i] + bx; } - mcstfPrim.bilateralWeightedFilter(srcPel, srcStride, numRefs, refBlkPtrs, refBlkStrides, vww, vsw, bitDepthDiffWeighting, maxSampleValue, + mcstfPrim.bilateralFilter(srcPel, srcStride, numRefs, refBlkPtrs, refBlkStrides, vww, vsw, bitDepthDiffWeighting, maxSampleValue, blkSize, srcPelPlane + by * srcStride + bx, srcStride); } } @@ -677,7 +659,7 @@ void TemporalFilter::bilateralFilterCore(Frame* frame, TemporalFilterRefPicInfo* } // Splits the frame into 64-row blocks, dispatches jobs to the threadpool via BilateralFilterGroup, then returns. -void TemporalFilter::bilateralFilter(Frame* curFrame, TemporalFilterRefPicInfo* mctfRefList, double overallStrength, ThreadPool* pool) +void TemporalFilter::bilateralFilter(Frame* curFrame, TemporalFilterRefPicInfo* mcstfRefList, double overallStrength, ThreadPool* pool) { const int numRef = curFrame->m_mcstf->m_numRef; const int rowSize = 64; @@ -689,14 +671,14 @@ void TemporalFilter::bilateralFilter(Frame* curFrame, TemporalFilterRefPicInfo* if (!pool) { - bilateralFilterCore(curFrame, mctfRefList, numRef, 0, 0, overallStrength); + bilateralFilterCore(curFrame, mcstfRefList, numRef, 0, 0, overallStrength); return; } BilateralFilterGroup filterGroup(*this, pool); for (int row = 0; row < numBlockRows; row++) - filterGroup.add(curFrame, mctfRefList, numRef, row, rowSize, overallStrength); + filterGroup.add(curFrame, mcstfRefList, numRef, row, rowSize, overallStrength); filterGroup.finishBatch(); } diff --git a/source/common/temporalfilter.h b/source/common/temporalfilter.h index 34becb04b..3fe1c7ed7 100644 --- a/source/common/temporalfilter.h +++ b/source/common/temporalfilter.h @@ -59,19 +59,20 @@ const double s_refStrengths[3][4] = {1.13, 0.97, 0.81, 0.57}, // m_range {0.30, 0.30, 0.30, 0.30} // otherwise }; +#define MCSTF_MAX_REFS 16 namespace X265_NS { /* MCSTF runtime SIMD dispatch * Function-pointer table for MCSTF SIMD kernels. Defaults are scalar - * implementations defined in temporalfilter.cpp; setupMCTFPrimitives_x86 + * implementations defined in temporalfilter.cpp; setupMCSTFPrimitives_x86 * (in common/x86/temporalfilter_simd.cpp) overrides them with SSE4.1/AVX2 * variants when the runtime CPU supports the required ISA.*/ struct MCSTFPrimitives { int (*motionErrorLumaFrac)(const pixel* origOrigin, intptr_t origStride, const pixel* buffOrigin, intptr_t buffStride, int x, int y, - int dx, int dy, int bs, int besterror, int bitDepth, int errorMode); + int dx, int dy, int bs, int besterror, int bitDepth); void (*applyMotion)(const pixel* pSrcImage, int srcStride, pixel* pDstImage, int dstStride, int width, int height, int blockSizeX, int blockSizeY, uint32_t mvsStride, const MV* mvs, int csx, int csy, int blockRow, int rowSize, int vShift); @@ -80,17 +81,13 @@ namespace X265_NS { void (*computeBlockStats)(const pixel* srcPel, intptr_t srcStride, const pixel* refPel, intptr_t refStride, int blkSize, int* outVariance, int* outDiffsum); - void (*bilateralWeightedFilter)(const pixel* srcBlk, intptr_t srcStride, int numRefs, const pixel* const* refBlks, const intptr_t* refStrides, + void (*bilateralFilter)(const pixel* srcBlk, intptr_t srcStride, int numRefs, const pixel* const* refBlks, const intptr_t* refStrides, const double* vww, const double* vsw, double bdw, double maxSample, int blkSize, pixel* dstBlk, intptr_t dstStride); }; -#define MCTF_MAX_REFS 16 extern MCSTFPrimitives mcstfPrim; - void setupMCTFPrimitives_scalar(MCSTFPrimitives& p); -#if X265_ARCH_X86 - void setupMCTFPrimitives_x86(MCSTFPrimitives& p, int cpuMask); -#endif + void setupMCSTFPrimitives_scalar(MCSTFPrimitives& p); class OrigPicBuffer { @@ -144,16 +141,6 @@ namespace X265_NS { int bs, int besterror = 8 * 8 * 1024 * 1024); - int motionErrorLumaSAD(MotionEstimatorTLD& m_tld, pixel* src, - int stride, - pixel* buf, - int x, - int y, - int dx, - int dy, - int bs, - int besterror = 8 * 8 * 1024 * 1024); - ~MotionEstimatorTLD() { predPUYuv.destroy(); } @@ -221,12 +208,12 @@ namespace X265_NS { int createRefPicInfo(TemporalFilterRefPicInfo* refFrame, x265_param* param); void bilateralFilter(Frame* frame, - TemporalFilterRefPicInfo* mctfRefList, + TemporalFilterRefPicInfo* mcstfRefList, double overallStrength, ThreadPool* pool); void bilateralFilterCore(Frame* frame, - TemporalFilterRefPicInfo* mctfRefList, + TemporalFilterRefPicInfo* mcstfRefList, int numRef, int blockRow, int blockSize, @@ -247,7 +234,7 @@ namespace X265_NS { struct FilterJob { Frame* frame; - TemporalFilterRefPicInfo* mctfRefList; + TemporalFilterRefPicInfo* mcstfRefList; int numRef; int blockRow; int rowSize; @@ -263,14 +250,14 @@ namespace X265_NS { BilateralFilterGroup(TemporalFilter& f, ThreadPool* pool) : m_filter(f), m_pool(pool), m_jobTotal(0), m_jobAcquired(0) {} - void add(Frame* frame, TemporalFilterRefPicInfo* mctfRefList, + void add(Frame* frame, TemporalFilterRefPicInfo* mcstfRefList, int numRef, int blockRow, int rowSize, double strength) { X265_CHECK(m_jobTotal < MAX_FILTER_JOBS, "BilateralFilterGroup overflow\n"); FilterJob& j = m_jobs[m_jobTotal++]; j.frame = frame; - j.mctfRefList = mctfRefList; + j.mcstfRefList = mcstfRefList; j.numRef = numRef; j.blockRow = blockRow; j.rowSize = rowSize; @@ -295,7 +282,7 @@ namespace X265_NS { m_lock.release(); const FilterJob& j = m_jobs[i]; - m_filter.bilateralFilterCore(j.frame, j.mctfRefList, j.numRef, + m_filter.bilateralFilterCore(j.frame, j.mcstfRefList, j.numRef, j.blockRow, j.rowSize, j.overallStrength); m_lock.acquire(); diff --git a/source/common/x86/temporalfilter_simd.cpp b/source/common/vec/temporalfilter-avx2.cpp similarity index 91% rename from source/common/x86/temporalfilter_simd.cpp rename to source/common/vec/temporalfilter-avx2.cpp index 91ee2cfe2..cbe7a407b 100644 --- a/source/common/x86/temporalfilter_simd.cpp +++ b/source/common/vec/temporalfilter-avx2.cpp @@ -21,21 +21,19 @@ * For more information, contact us at license @ x265.com. *****************************************************************************/ -/* MCTF SIMD kernels - Provides AVX2 implementations of MCSTF primitives - * declared in temporalfilter.h (MCTFPrimitives). +/* MCSTF SIMD kernels - Provides AVX2 implementations of MCSTF primitives + * declared in temporalfilter.h (MCSTFPrimitives). * - * Entry point: setupMCTFPrimitives_x86(MCTFPrimitives&, int cpuMask) - * Called from primitives.cpp after setupMCTFPrimitives_scalar(). + * Entry point: setupMCSTFPrimitives_x86(MCSTFPrimitives&, int cpuMask) + * Called from primitives.cpp after setupMCSTFPrimitives_scalar(). * Overrides only the function pointers whose ISA requirement is met. */ -#if ENABLE_ASSEMBLY && X265_ARCH_X86 #include /* SSE2 */ #include /* SSSE3 */ #include /* SSE4.1 */ #include /* AVX2 */ -#include #include "common.h" #include "primitives.h" @@ -43,6 +41,9 @@ #include "mv.h" #include "cpu.h" +using namespace X265_NS; + +namespace X265_NS { #if defined(_MSC_VER) && !defined(__clang__) && !defined(__INTEL_COMPILER) # define MCSTF_TARGET_AVX2 @@ -50,7 +51,6 @@ # define MCSTF_TARGET_AVX2 __attribute__((target("avx2"))) #endif -namespace X265_NS { /* Shared helpers */ @@ -87,9 +87,9 @@ namespace X265_NS { v = _mm_add_epi32(v, hi); return _mm_cvtsi128_si32(v); } - - MCSTF_TARGET_AVX2 - static inline int hsum_epi32_avx(__m256i v) + + MCSTF_TARGET_AVX2 + static inline int hsum_epi32_avx(__m256i v) { __m128i lo = _mm256_castsi256_si128(v); __m128i hi = _mm256_extracti128_si256(v, 1); @@ -158,19 +158,23 @@ namespace X265_NS { } MCSTF_TARGET_AVX2 - static int motionErrorLumaFrac_AVX2( + static int motionErrorLumaFrac_avx2( const pixel* origOrigin, intptr_t origStride, const pixel* buffOrigin, intptr_t buffStride, int x, int y, int dx, int dy, - int bs, int besterror, int bitDepth, int errorMode) + int bs, int besterror, int bitDepth) { const int* xFilter = s_interpolationFilter[dx & 0xF]; const int* yFilter = s_interpolationFilter[dy & 0xF]; + X265_CHECK(bs <= 16, "Unsupported block size\n"); int tempArray[64 + 8][64]; const int int_dx = dx >> 4; const int int_dy = dy >> 4; + int error = 0; + // HEVC interpolation filters always have zero-valued end taps + // (xFilter[0] and xFilter[7]), so only taps 1..6 are processed. const __m128i xf12_128 = _mm_unpacklo_epi16( _mm_set1_epi16((int16_t)xFilter[1]), _mm_set1_epi16((int16_t)xFilter[2])); @@ -197,11 +201,11 @@ namespace X265_NS { #if X265_DEPTH > 8 - /* uint16 pixels: load 8× uint16 directly into 128-bit reg. - * _mm256_set_m128i places lo_half in [127:0] and hi_half in - * [255:128]. We build the 256-bit interleaved pairs: - * pairs_ab[255:0] = { unpackhi(s_a, s_b) | unpacklo(s_a, s_b) } - * so madd lane i gives s_a[i]*f_a + s_b[i]*f_b. */ + /* uint16 pixels: load 8× uint16 directly into 128-bit reg. + * _mm256_set_m128i places lo_half in [127:0] and hi_half in + * [255:128]. We build the 256-bit interleaved pairs: + * pairs_ab[255:0] = { unpackhi(s_a, s_b) | unpacklo(s_a, s_b) } + * so madd lane i gives s_a[i]*f_a + s_b[i]*f_b. */ __m128i s1 = _mm_loadu_si128((const __m128i*) & rowStart[1]); __m128i s2 = _mm_loadu_si128((const __m128i*) & rowStart[2]); __m128i s3 = _mm_loadu_si128((const __m128i*) & rowStart[3]); @@ -237,6 +241,7 @@ namespace X265_NS { } } + // End taps yFilter[0] and yFilter[7] are always zero. const __m256i yt1 = _mm256_set1_epi32(yFilter[1]); const __m256i yt2 = _mm256_set1_epi32(yFilter[2]); const __m256i yt3 = _mm256_set1_epi32(yFilter[3]); @@ -286,26 +291,22 @@ namespace X265_NS { __m256i diff = _mm256_sub_epi32(v, orig); - __m256i row_err; - if (errorMode == 0) /* SAD */ - row_err = _mm256_abs_epi32(diff); - else /* SSD */ - row_err = _mm256_mullo_epi32(diff, diff); + __m256i row_err = _mm256_mullo_epi32(diff, diff); /* SSD */ xerror = _mm256_add_epi32(xerror, row_err); } - int error = hsum_epi32_avx(xerror); + error = hsum_epi32_avx(xerror); if (error > besterror) return error; } - return hsum_epi32_avx(xerror); + return error; } /* Per-block separable 6-tap filter worker */ MCSTF_TARGET_AVX2 - static void applyMotionBlock_AVX2( + static void applyMotionBlock_avx2( const pixel* pSrcImage, int srcStride, pixel* pDstImage, int dstStride, int x, int y, @@ -314,7 +315,11 @@ namespace X265_NS { const int* xFilter, const int* yFilter) { - int tempArray[15][8]; + X265_CHECK(blockSizeX == 4 || blockSizeX == 8, "Unsupported block width\n"); + + static const int numFilterTaps = 7; + static const int maxBlockSize = 8; + int tempArray[maxBlockSize + numFilterTaps][8]; const __m256i xf1 = _mm256_set1_epi32(xFilter[1]); const __m256i xf2 = _mm256_set1_epi32(xFilter[2]); @@ -335,7 +340,7 @@ namespace X265_NS { const __m256i vmax = _mm256_set1_epi32((1 << X265_DEPTH) - 1); const int hColBase = x + xInt - 3; - const int hRowEnd = blockSizeY + 6; + const int hRowEnd = blockSizeY + 5; /* H-PASS */ if (blockSizeX == 8) @@ -447,7 +452,7 @@ namespace X265_NS { } MCSTF_TARGET_AVX2 - static void applyMotion_AVX2( + static void applyMotion_avx2( const pixel* pSrcImage, int srcStride, pixel* pDstImage, int dstStride, int width, int height, @@ -481,14 +486,16 @@ namespace X265_NS { const int* xFilter = s_interpolationFilter[dx & 0xf]; const int* yFilter = s_interpolationFilter[dy & 0xf]; - applyMotionBlock_AVX2( pSrcImage, srcStride, pDstImage, dstStride, x, y, blockSizeX, blockSizeY, xInt, yInt, xFilter, yFilter); + applyMotionBlock_avx2(pSrcImage, srcStride, pDstImage, dstStride, x, y, blockSizeX, blockSizeY, xInt, yInt, xFilter, yFilter); } } } MCSTF_TARGET_AVX2 - static void lumaBlockAvgVariance_AVX2(const pixel* origin, intptr_t stride, int blockX,int blockY,int blockSize,double* avgOut, double* varianceOut) + static void lumaBlockAvgVariance_avx2(const pixel* origin, intptr_t stride, int blockX, int blockY, int blockSize, double* avgOut, double* varianceOut) { + X265_CHECK((blockSize & 7) == 0, "Supports blockSize multiple of 8"); + const pixel* base = origin + blockY * stride + blockX; __m256i sumAcc = _mm256_setzero_si256(); @@ -565,7 +572,7 @@ namespace X265_NS { } MCSTF_TARGET_AVX2 - void computeBlockStats_AVX2( const pixel* srcPel, intptr_t srcStride, const pixel* refPel, intptr_t refStride, int blkSize, int* outVariance, int* outDiffsum) + static void computeBlockStats_avx2(const pixel* srcPel, intptr_t srcStride, const pixel* refPel, intptr_t refStride, int blkSize, int* outVariance, int* outDiffsum) { if (blkSize == 8) { @@ -654,7 +661,7 @@ namespace X265_NS { } MCSTF_TARGET_AVX2 - void bilateralWeightedFilter_AVX2( + static void bilateralFilter_avx2( const pixel* srcBlk, intptr_t srcStride, int numRefs, const pixel* const* refBlks, @@ -672,7 +679,7 @@ namespace X265_NS { const __m256d vMax = _mm256_set1_pd(maxSample); const __m256d vHalf = _mm256_set1_pd(0.5); - double neg_inv_vsw[MCTF_MAX_REFS]; + double neg_inv_vsw[MCSTF_MAX_REFS]; for (int i = 0; i < numRefs; i++) neg_inv_vsw[i] = -1.0 / vsw[i]; @@ -719,19 +726,15 @@ namespace X265_NS { } } } +} // anonymous namespace - /* Dispatch - overrides scalar defaults with SIMD variants at runtime */ - void setupMCTFPrimitives_x86(MCSTFPrimitives & p, int cpuMask) +namespace X265_NS { + void setupIntrinsicMCSTF_avx2(MCSTFPrimitives & p) { - if (cpuMask & X265_CPU_AVX2) - { - p.motionErrorLumaFrac = motionErrorLumaFrac_AVX2; - p.applyMotion = applyMotion_AVX2; - p.lumaBlockAvgVariance = lumaBlockAvgVariance_AVX2; - p.computeBlockStats = computeBlockStats_AVX2; - p.bilateralWeightedFilter = bilateralWeightedFilter_AVX2; - } + p.motionErrorLumaFrac = motionErrorLumaFrac_avx2; + p.applyMotion = applyMotion_avx2; + p.lumaBlockAvgVariance = lumaBlockAvgVariance_avx2; + p.computeBlockStats = computeBlockStats_avx2; + p.bilateralFilter = bilateralFilter_avx2; } -} /* namespace X265_NS */ - -#endif /* ENABLE_ASSEMBLY && X265_ARCH_X86 */ \ No newline at end of file +} // namespace X265_NS \ No newline at end of file diff --git a/source/common/vec/vec-primitives.cpp b/source/common/vec/vec-primitives.cpp index d37f7ac3d..c1d951cca 100644 --- a/source/common/vec/vec-primitives.cpp +++ b/source/common/vec/vec-primitives.cpp @@ -23,6 +23,7 @@ #include "primitives.h" #include "x265.h" +#include "temporalfilter.h" /* The #if logic here must match the file lists in CMakeLists.txt */ #if X265_ARCH_X86 @@ -57,6 +58,7 @@ namespace X265_NS { void setupIntrinsicDCT_sse3(EncoderPrimitives&); void setupIntrinsicDCT_ssse3(EncoderPrimitives&); void setupIntrinsicDCT_sse41(EncoderPrimitives&); +void setupIntrinsicMCSTF_avx2(MCSTFPrimitives&); /* Use primitives for the best available vector architecture */ void setupIntrinsicPrimitives(EncoderPrimitives &p, int cpuMask) @@ -78,6 +80,10 @@ void setupIntrinsicPrimitives(EncoderPrimitives &p, int cpuMask) { setupIntrinsicDCT_sse41(p); } +#endif +#ifdef HAVE_AVX2 + if (cpuMask & X265_CPU_AVX2) + setupIntrinsicMCSTF_avx2(mcstfPrim); #endif (void)p; (void)cpuMask; diff --git a/source/x265.h b/source/x265.h index 5e49645ce..a3b8800a9 100644 --- a/source/x265.h +++ b/source/x265.h @@ -2339,7 +2339,7 @@ typedef struct x265_param /*Motion compensated temporal filter*/ int bEnableTemporalFilter; double temporalFilterStrength; - /*Search Range for L0, L1 and L2 in MCTF*/ + /*Search Range for L0, L1 and L2 in MCSTF*/ int searchRangeForLayer0; int searchRangeForLayer1; int searchRangeForLayer2; From 66fe2ac01a2f5447e345569fa5d036073ffad1fb Mon Sep 17 00:00:00 2001 From: Kirithika7 Date: Tue, 14 Jul 2026 15:39:20 +0530 Subject: [PATCH 16/23] test:Add testbench support for MCSTF --- source/common/temporalfilter.h | 11 +- source/test/CMakeLists.txt | 3 +- source/test/mcstfharness.cpp | 304 +++++++++++++++++++++++++++++++++ source/test/mcstfharness.h | 63 +++++++ source/test/testbench.cpp | 45 ++++- 5 files changed, 421 insertions(+), 5 deletions(-) create mode 100644 source/test/mcstfharness.cpp create mode 100644 source/test/mcstfharness.h diff --git a/source/common/temporalfilter.h b/source/common/temporalfilter.h index 3fe1c7ed7..74c286871 100644 --- a/source/common/temporalfilter.h +++ b/source/common/temporalfilter.h @@ -65,9 +65,9 @@ namespace X265_NS { /* MCSTF runtime SIMD dispatch * Function-pointer table for MCSTF SIMD kernels. Defaults are scalar - * implementations defined in temporalfilter.cpp; setupMCSTFPrimitives_x86 - * (in common/x86/temporalfilter_simd.cpp) overrides them with SSE4.1/AVX2 - * variants when the runtime CPU supports the required ISA.*/ + * implementations defined in temporalfilter.cpp; setupIntrinsicMCSTF_avx2 + * (in common/vec/temporalfilter-avx2.cpp) overrides them with AVX2 variants, + * called from setupIntrinsicPrimitives() when the runtime CPU supports it.*/ struct MCSTFPrimitives { @@ -88,6 +88,11 @@ namespace X265_NS { extern MCSTFPrimitives mcstfPrim; void setupMCSTFPrimitives_scalar(MCSTFPrimitives& p); +#if X265_ARCH_X86 + /* defined in common/vec/temporalfilter-avx2.cpp; exposed here so the test + * bench can exercise it directly without going through the global mcstfPrim */ + void setupIntrinsicMCSTF_avx2(MCSTFPrimitives& p); +#endif class OrigPicBuffer { diff --git a/source/test/CMakeLists.txt b/source/test/CMakeLists.txt index 1da47dd67..6cd058313 100644 --- a/source/test/CMakeLists.txt +++ b/source/test/CMakeLists.txt @@ -53,7 +53,8 @@ add_executable(TestBench ${NASM_SRC} pixelharness.cpp pixelharness.h mbdstharness.cpp mbdstharness.h ipfilterharness.cpp ipfilterharness.h - intrapredharness.cpp intrapredharness.h) + intrapredharness.cpp intrapredharness.h + mcstfharness.cpp mcstfharness.h) target_link_libraries(TestBench x265-static ${PLATFORM_LIBS}) if(LINKER_OPTIONS) diff --git a/source/test/mcstfharness.cpp b/source/test/mcstfharness.cpp new file mode 100644 index 000000000..251a9e026 --- /dev/null +++ b/source/test/mcstfharness.cpp @@ -0,0 +1,304 @@ +/***************************************************************************** + * Copyright (C) 2013-2021 MulticoreWare, Inc + * + * Authors: Kirithika Kalirathnam + + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA. + * + * This program is also available under a commercial proprietary license. + * For more information, contact us at license @ x265.com. + *****************************************************************************/ + +#include "common.h" +#include "mcstfharness.h" + +using namespace X265_NS; + +/* Fixed interior position/margins used by every check below: the buffers are + * TEST_STRIDE x TEST_STRIDE with CENTER chosen so that the widest kernel + * (bs = 16, +/- MV_PIXEL_RANGE integer MV, +/- 3 tap padding) never reads + * outside the allocated buffers. */ +static const int CENTER = 40; +static const int MV_PIXEL_RANGE = 4; /* max integer part of a test MV, in pixels */ +static const int PAD = 16; /* margin reserved for applyMotion's plane origin */ + +MCSTFHarness::MCSTFHarness() +{ + for (int i = 0; i < TEST_BUF_SIZE; i++) + { + m_origBuf[i] = (pixel)(rand() % (PIXEL_MAX + 1)); + m_refBuf[i] = (pixel)(rand() % (PIXEL_MAX + 1)); + } +} + +bool MCSTFHarness::check_motionErrorLumaFrac(const MCSTFPrimitives& ref, const MCSTFPrimitives& opt) +{ + const int bsValues[] = { 8, 16 }; + const int bestErrorValues[] = { (8 * 8 * 1024 * 1024), 100 }; + + for (size_t bi = 0; bi < sizeof(bsValues) / sizeof(int); bi++) + { + int bs = bsValues[bi]; + + for (int i = 0; i < ITERS; i++) + { + int dx = (rand() % (2 * MV_PIXEL_RANGE * 16 + 1)) - MV_PIXEL_RANGE * 16; + int dy = (rand() % (2 * MV_PIXEL_RANGE * 16 + 1)) - MV_PIXEL_RANGE * 16; + int besterror = bestErrorValues[i % 2]; + + int resRef = ref.motionErrorLumaFrac( + m_origBuf, TEST_STRIDE, m_refBuf, TEST_STRIDE, + CENTER, CENTER, dx, dy, bs, besterror, X265_DEPTH); + int resOpt = opt.motionErrorLumaFrac( + m_origBuf, TEST_STRIDE, m_refBuf, TEST_STRIDE, + CENTER, CENTER, dx, dy, bs, besterror, X265_DEPTH); + + if (resRef != resOpt) + { + printf("motionErrorLumaFrac[bs=%d]: failed (dx=%d dy=%d besterror=%d ref=%d opt=%d)\n", + bs, dx, dy, besterror, resRef, resOpt); + return false; + } + } + } + return true; +} + +bool MCSTFHarness::check_applyMotion(const MCSTFPrimitives& ref, const MCSTFPrimitives& opt) +{ + struct Cfg { int blockSizeX, blockSizeY, csx, csy; }; + const Cfg cfgs[] = { { 8, 8, 0, 0 }, { 4, 4, 1, 1 } }; + + const int width = 64, height = 64; + pixel* planeOrigin = m_origBuf + PAD * TEST_STRIDE + PAD; + + for (size_t ci = 0; ci < sizeof(cfgs) / sizeof(Cfg); ci++) + { + const Cfg& cfg = cfgs[ci]; + const int mvStride = width / cfg.blockSizeX; + const int mvRows = height / cfg.blockSizeY; + + MV mvs[256]; /* worst case: 64/4 x 64/4 chroma grid */ + for (int i = 0; i < mvStride * mvRows; i++) + mvs[i] = MV((rand() % (2 * MV_PIXEL_RANGE * 16 + 1)) - MV_PIXEL_RANGE * 16, + (rand() % (2 * MV_PIXEL_RANGE * 16 + 1)) - MV_PIXEL_RANGE * 16); + + /* whole-frame call (rowSize = 0) and a row-batched call, matching the + * two ways TemporalFilter::applyMotion is actually driven */ + const struct { int blockRow, rowSize, vShift; } rowCfgs[] = + { + { 0, 0, 0 }, + { 1, 16, cfg.csy }, + }; + + for (size_t ri = 0; ri < sizeof(rowCfgs) / sizeof(rowCfgs[0]); ri++) + { + memset(m_dstBufRef, 0, sizeof(m_dstBufRef)); + memset(m_dstBufOpt, 0, sizeof(m_dstBufOpt)); + pixel* dstOriginRef = m_dstBufRef + PAD * TEST_STRIDE + PAD; + pixel* dstOriginOpt = m_dstBufOpt + PAD * TEST_STRIDE + PAD; + + ref.applyMotion(planeOrigin, TEST_STRIDE, dstOriginRef, TEST_STRIDE, + width, height, cfg.blockSizeX, cfg.blockSizeY, mvStride, mvs, + cfg.csx, cfg.csy, rowCfgs[ri].blockRow, rowCfgs[ri].rowSize, rowCfgs[ri].vShift); + opt.applyMotion(planeOrigin, TEST_STRIDE, dstOriginOpt, TEST_STRIDE, + width, height, cfg.blockSizeX, cfg.blockSizeY, mvStride, mvs, + cfg.csx, cfg.csy, rowCfgs[ri].blockRow, rowCfgs[ri].rowSize, rowCfgs[ri].vShift); + + if (memcmp(m_dstBufRef, m_dstBufOpt, sizeof(m_dstBufRef))) + { + printf("applyMotion[bx=%d by=%d rowSize=%d]: failed\n", + cfg.blockSizeX, cfg.blockSizeY, rowCfgs[ri].rowSize); + return false; + } + } + } + return true; +} + +bool MCSTFHarness::check_lumaBlockAvgVariance(const MCSTFPrimitives& ref, const MCSTFPrimitives& opt) +{ + const int blockSizes[] = { 8, 16 }; + + for (size_t bi = 0; bi < sizeof(blockSizes) / sizeof(int); bi++) + { + int blockSize = blockSizes[bi]; + + for (int i = 0; i < ITERS; i++) + { + double avgRef, varRef, avgOpt, varOpt; + ref.lumaBlockAvgVariance(m_origBuf, TEST_STRIDE, CENTER, CENTER, blockSize, &avgRef, &varRef); + opt.lumaBlockAvgVariance(m_origBuf, TEST_STRIDE, CENTER, CENTER, blockSize, &avgOpt, &varOpt); + + double avgTol = 1e-6 * X265_MAX(1.0, fabs(avgRef)); + double varTol = 1e-6 * X265_MAX(1.0, fabs(varRef)); + + if (fabs(avgRef - avgOpt) > avgTol || fabs(varRef - varOpt) > varTol) + { + printf("lumaBlockAvgVariance[bs=%d]: failed (avg %.6f vs %.6f, var %.6f vs %.6f)\n", + blockSize, avgRef, avgOpt, varRef, varOpt); + return false; + } + } + } + return true; +} + +bool MCSTFHarness::check_computeBlockStats(const MCSTFPrimitives& ref, const MCSTFPrimitives& opt) +{ + const int blkSizes[] = { 4, 8 }; + + for (size_t bi = 0; bi < sizeof(blkSizes) / sizeof(int); bi++) + { + int blkSize = blkSizes[bi]; + + for (int i = 0; i < ITERS; i++) + { + int varRef, diffRef, varOpt, diffOpt; + ref.computeBlockStats(m_origBuf + CENTER * TEST_STRIDE + CENTER, TEST_STRIDE, + m_refBuf + CENTER * TEST_STRIDE + CENTER, TEST_STRIDE, blkSize, &varRef, &diffRef); + opt.computeBlockStats(m_origBuf + CENTER * TEST_STRIDE + CENTER, TEST_STRIDE, + m_refBuf + CENTER * TEST_STRIDE + CENTER, TEST_STRIDE, blkSize, &varOpt, &diffOpt); + + if (varRef != varOpt || diffRef != diffOpt) + { + printf("computeBlockStats[bs=%d]: failed (variance %d vs %d, diffsum %d vs %d)\n", + blkSize, varRef, varOpt, diffRef, diffOpt); + return false; + } + } + } + return true; +} + +bool MCSTFHarness::check_bilateralFilter(const MCSTFPrimitives& ref, const MCSTFPrimitives& opt) +{ + const int blkSizes[] = { 4, 8 }; + const int numRefsValues[] = { 1, 2, 4 }; + const double bdw = 1024.0 / (PIXEL_MAX + 1); + const double maxSample = PIXEL_MAX; + + for (size_t bi = 0; bi < sizeof(blkSizes) / sizeof(int); bi++) + { + int blkSize = blkSizes[bi]; + + for (size_t ni = 0; ni < sizeof(numRefsValues) / sizeof(int); ni++) + { + int numRefs = numRefsValues[ni]; + + const pixel* refBlks[4]; + intptr_t refStrides[4]; + double vww[4], vsw[4]; + for (int i = 0; i < numRefs; i++) + { + refBlks[i] = m_refBuf + CENTER * TEST_STRIDE + CENTER + i * 4; + refStrides[i] = TEST_STRIDE; + vww[i] = 0.1 + (rand() % 100) / 100.0; + vsw[i] = 50.0 + (rand() % 500); + } + + const pixel* srcBlk = m_origBuf + CENTER * TEST_STRIDE + CENTER; + + memset(m_dstBufRef, 0, sizeof(m_dstBufRef)); + memset(m_dstBufOpt, 0, sizeof(m_dstBufOpt)); + + ref.bilateralFilter(srcBlk, TEST_STRIDE, numRefs, refBlks, refStrides, + vww, vsw, bdw, maxSample, blkSize, m_dstBufRef, TEST_STRIDE); + opt.bilateralFilter(srcBlk, TEST_STRIDE, numRefs, refBlks, refStrides, + vww, vsw, bdw, maxSample, blkSize, m_dstBufOpt, TEST_STRIDE); + + for (int y = 0; y < blkSize; y++) + { + if (memcmp(m_dstBufRef + y * TEST_STRIDE, m_dstBufOpt + y * TEST_STRIDE, blkSize * sizeof(pixel))) + { + printf("bilateralFilter[bs=%d numRefs=%d]: failed\n", blkSize, numRefs); + return false; + } + } + } + } + return true; +} + +bool MCSTFHarness::testCorrectness(const MCSTFPrimitives& ref, const MCSTFPrimitives& opt) +{ + if (opt.motionErrorLumaFrac && !check_motionErrorLumaFrac(ref, opt)) + return false; + if (opt.applyMotion && !check_applyMotion(ref, opt)) + return false; + if (opt.lumaBlockAvgVariance && !check_lumaBlockAvgVariance(ref, opt)) + return false; + if (opt.computeBlockStats && !check_computeBlockStats(ref, opt)) + return false; + if (opt.bilateralFilter && !check_bilateralFilter(ref, opt)) + return false; + return true; +} + +void MCSTFHarness::measureSpeed(const MCSTFPrimitives& ref, const MCSTFPrimitives& opt) +{ + if (opt.motionErrorLumaFrac) + { + printf("motionErrorLumaFrac[bs=16]\t"); + REPORT_SPEEDUP(opt.motionErrorLumaFrac, ref.motionErrorLumaFrac, + m_origBuf, TEST_STRIDE, m_refBuf, TEST_STRIDE, CENTER, CENTER, 20, 5, 16, INT_MAX, X265_DEPTH); + } + + if (opt.applyMotion) + { + static MV mvs[64]; + pixel* planeOrigin = m_origBuf + PAD * TEST_STRIDE + PAD; + printf("applyMotion[8x8]\t"); + REPORT_SPEEDUP(opt.applyMotion, ref.applyMotion, + planeOrigin, TEST_STRIDE, m_dstBufOpt, TEST_STRIDE, 64, 64, 8, 8, 8, mvs, 0, 0, 0, 0, 0); + } + + if (opt.lumaBlockAvgVariance) + { + double avg, variance; + printf("lumaBlockAvgVariance[bs=8]\t"); + REPORT_SPEEDUP(opt.lumaBlockAvgVariance, ref.lumaBlockAvgVariance, + m_origBuf, TEST_STRIDE, CENTER, CENTER, 8, &avg, &variance); + } + + if (opt.computeBlockStats) + { + int variance, diffsum; + printf("computeBlockStats[bs=8]\t"); + REPORT_SPEEDUP(opt.computeBlockStats, ref.computeBlockStats, + m_origBuf + CENTER * TEST_STRIDE + CENTER, TEST_STRIDE, + m_refBuf + CENTER * TEST_STRIDE + CENTER, TEST_STRIDE, 8, &variance, &diffsum); + } + + if (opt.bilateralFilter) + { + const pixel* refBlks[4]; + intptr_t refStrides[4]; + double vww[4], vsw[4]; + for (int i = 0; i < 4; i++) + { + refBlks[i] = m_refBuf + CENTER * TEST_STRIDE + CENTER + i * 4; + refStrides[i] = TEST_STRIDE; + vww[i] = 1.0; + vsw[i] = 200.0; + } + const pixel* srcBlk = m_origBuf + CENTER * TEST_STRIDE + CENTER; + const double bdw = 1024.0 / (PIXEL_MAX + 1); + + printf("bilateralFilter[bs=8,refs=4]\t"); + REPORT_SPEEDUP(opt.bilateralFilter, ref.bilateralFilter, + srcBlk, TEST_STRIDE, 4, refBlks, refStrides, vww, vsw, bdw, (double)PIXEL_MAX, 8, m_dstBufOpt, TEST_STRIDE); + } +} diff --git a/source/test/mcstfharness.h b/source/test/mcstfharness.h new file mode 100644 index 000000000..0ba4edaf2 --- /dev/null +++ b/source/test/mcstfharness.h @@ -0,0 +1,63 @@ +/***************************************************************************** + * Copyright (C) 2013-2021 MulticoreWare, Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA. + * + * This program is also available under a commercial proprietary license. + * For more information, contact us at license @ x265.com. + *****************************************************************************/ + +#ifndef _MCSTFHARNESS_H_1 +#define _MCSTFHARNESS_H_1 1 + +#include "testharness.h" +#include "temporalfilter.h" + +/* MCSTFPrimitives is a standalone function-pointer table (not part of + * EncoderPrimitives), and it only ever has one SIMD tier (AVX2), so this + * harness does not fit the TestHarness(EncoderPrimitives) interface used by + * the rest of the test bench. It is driven directly from testbench.cpp + * instead of through the generic harness[] loop. */ +class MCSTFHarness +{ +protected: + + enum { TEST_STRIDE = 96 }; + enum { TEST_BUF_SIZE = TEST_STRIDE * TEST_STRIDE }; + enum { ITERS = 32 }; + + ALIGN_VAR_32(pixel, m_origBuf[TEST_BUF_SIZE]); + ALIGN_VAR_32(pixel, m_refBuf[TEST_BUF_SIZE]); + ALIGN_VAR_32(pixel, m_dstBufRef[TEST_BUF_SIZE]); + ALIGN_VAR_32(pixel, m_dstBufOpt[TEST_BUF_SIZE]); + + bool check_motionErrorLumaFrac(const MCSTFPrimitives& ref, const MCSTFPrimitives& opt); + bool check_applyMotion(const MCSTFPrimitives& ref, const MCSTFPrimitives& opt); + bool check_lumaBlockAvgVariance(const MCSTFPrimitives& ref, const MCSTFPrimitives& opt); + bool check_computeBlockStats(const MCSTFPrimitives& ref, const MCSTFPrimitives& opt); + bool check_bilateralFilter(const MCSTFPrimitives& ref, const MCSTFPrimitives& opt); + +public: + + MCSTFHarness(); + + const char *getName() const { return "mcstf"; } + + bool testCorrectness(const MCSTFPrimitives& ref, const MCSTFPrimitives& opt); + + void measureSpeed(const MCSTFPrimitives& ref, const MCSTFPrimitives& opt); +}; + +#endif // ifndef _MCSTFHARNESS_H_1 diff --git a/source/test/testbench.cpp b/source/test/testbench.cpp index 3a2ac7a58..fb12e340d 100644 --- a/source/test/testbench.cpp +++ b/source/test/testbench.cpp @@ -31,6 +31,7 @@ #include "mbdstharness.h" #include "ipfilterharness.h" #include "intrapredharness.h" +#include "mcstfharness.h" #include "param.h" #include "cpu.h" @@ -131,7 +132,7 @@ void do_help() printf("usage: TestBench [--cpuid CPU] [--testbench BENCH] [--nobench] [--help]\n\n"); printf(" CPU is comma separated SIMD architecture list, for example: SSE4,AVX\n"); printf(" Use `--cpuid list` to print a list of detected SIMD architectures\n\n"); - printf(" BENCH is one of (pixel,transforms,interp,intrapred)\n\n"); + printf(" BENCH is one of (pixel,transforms,interp,intrapred,mcstf)\n\n"); printf(" `--nobench` disables running benchmarks, only run correctness tests\n\n"); printf("By default, the test bench will test all benches on detected CPU architectures\n"); printf("Options and testbench name may be truncated.\n"); @@ -141,6 +142,7 @@ PixelHarness HPixel; MBDstHarness HMBDist; IPFilterHarness HIPFilter; IntraPredHarness HIPred; +MCSTFHarness HMCSTF; int main(int argc, char *argv[]) { @@ -269,6 +271,30 @@ int main(int argc, char *argv[]) } } +#if X265_ARCH_X86 + /* MCSTFPrimitives is a standalone table with a single SIMD tier (AVX2), so + * it is tested once against the effective (possibly --cpuid restricted) + * cpuid rather than through the per-arch EncoderPrimitives loop above. */ + if (!testname || !strncmp(testname, HMCSTF.getName(), strlen(testname))) + { + MCSTFPrimitives mcstfCRef; + memset(&mcstfCRef, 0, sizeof(mcstfCRef)); + setupMCSTFPrimitives_scalar(mcstfCRef); + + MCSTFPrimitives mcstfOpt; + memset(&mcstfOpt, 0, sizeof(mcstfOpt)); + if (cpuid & X265_CPU_AVX2) + setupIntrinsicMCSTF_avx2(mcstfOpt); + + if (!HMCSTF.testCorrectness(mcstfCRef, mcstfOpt)) + { + fflush(stdout); + fprintf(stderr, "\nx265: mcstf primitive has failed. Go and fix that Right Now!\n"); + return -1; + } + } +#endif + /******************* Cycle count for all primitives **********************/ if (run_benchmarks) { @@ -299,6 +325,23 @@ int main(int argc, char *argv[]) harness[h]->measureSpeed(cprim, optprim); } +#if X265_ARCH_X86 + if (!testname || !strncmp(testname, HMCSTF.getName(), strlen(testname))) + { + MCSTFPrimitives mcstfCRef; + memset(&mcstfCRef, 0, sizeof(mcstfCRef)); + setupMCSTFPrimitives_scalar(mcstfCRef); + + MCSTFPrimitives mcstfOpt; + memset(&mcstfOpt, 0, sizeof(mcstfOpt)); + if (cpuid & X265_CPU_AVX2) + setupIntrinsicMCSTF_avx2(mcstfOpt); + + printf("== %s primitives ==\n", HMCSTF.getName()); + HMCSTF.measureSpeed(mcstfCRef, mcstfOpt); + } +#endif + printf("\n"); } return 0; From 9f5c5769524ca771e2fd557dc80ff8bd74dc1c68 Mon Sep 17 00:00:00 2001 From: Kirithika7 Date: Wed, 15 Jul 2026 11:56:26 +0530 Subject: [PATCH 17/23] cleanup: Rename MCSTF subsample function --- source/common/lowres.cpp | 6 +++--- source/common/pixel.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/common/lowres.cpp b/source/common/lowres.cpp index 57dfa2c89..856321069 100644 --- a/source/common/lowres.cpp +++ b/source/common/lowres.cpp @@ -28,7 +28,7 @@ namespace X265_NS { // forward declaration - defined in pixel.cpp, no SIMD override for MCSTF path - void frame_init_lowres_core_mcstf(const pixel* src0, pixel* dst0, pixel* dsth, pixel* dstv, pixel* dstc, + void frameInitLowresCoreMCSTF(const pixel* src0, pixel* dst0, pixel* dsth, pixel* dstv, pixel* dstc, intptr_t src_stride, intptr_t dst_stride, int width, int height); } @@ -387,7 +387,7 @@ void Lowres::init(PicYuv* origPic, int poc, bool bEnableTemporalFilter) /* downscale and generate 4 hpel planes for lookahead */ if (bEnableTemporalFilter) - frame_init_lowres_core_mcstf(origPic->m_picOrg[0], lowresPlane[0], lowresPlane[1], lowresPlane[2], lowresPlane[3], origPic->m_stride, lumaStride, width, lines); + frameInitLowresCoreMCSTF(origPic->m_picOrg[0], lowresPlane[0], lowresPlane[1], lowresPlane[2], lowresPlane[3], origPic->m_stride, lumaStride, width, lines); else primitives.frameInitLowres(origPic->m_picOrg[0], lowresPlane[0], lowresPlane[1], lowresPlane[2], lowresPlane[3], origPic->m_stride, lumaStride, width, lines); @@ -400,7 +400,7 @@ void Lowres::init(PicYuv* origPic, int poc, bool bEnableTemporalFilter) if (origPic->m_param->bEnableHME || origPic->m_param->bEnableTemporalFilter) { if (bEnableTemporalFilter) - frame_init_lowres_core_mcstf(lowresPlane[0], lowerResPlane[0], lowerResPlane[1], lowerResPlane[2], lowerResPlane[3], lumaStride, lumaStride / 2, (width / 2), (lines / 2)); + frameInitLowresCoreMCSTF(lowresPlane[0], lowerResPlane[0], lowerResPlane[1], lowerResPlane[2], lowerResPlane[3], lumaStride, lumaStride / 2, (width / 2), (lines / 2)); else primitives.frameInitLowerRes(lowresPlane[0], lowerResPlane[0], lowerResPlane[1], lowerResPlane[2], lowerResPlane[3], lumaStride, lumaStride / 2, (width / 2), (lines / 2)); diff --git a/source/common/pixel.cpp b/source/common/pixel.cpp index 04543434b..e90ef5207 100644 --- a/source/common/pixel.cpp +++ b/source/common/pixel.cpp @@ -1014,7 +1014,7 @@ namespace X265_NS { // x265 private namespace /* Scalar lowres downscale for MCSTF path - HM-equivalent filter, no SIMD override */ - void frame_init_lowres_core_mcstf(const pixel* src0, pixel* dst0, pixel* dsth, pixel* dstv, pixel* dstc, + void frameInitLowresCoreMCSTF(const pixel* src0, pixel* dst0, pixel* dsth, pixel* dstv, pixel* dstc, intptr_t src_stride, intptr_t dst_stride, int width, int height) { for (int y = 0; y < height; y++) From 42632ed3f9dc7500bab097794b5ed53d6fef07c5 Mon Sep 17 00:00:00 2001 From: Kirithika7 Date: Tue, 7 Jul 2026 16:00:13 +0530 Subject: [PATCH 18/23] mcstf: Decouple MCSTF HME from badapt flow --- source/encoder/encoder.cpp | 5 ----- source/encoder/slicetype.cpp | 13 +++++++++---- source/encoder/slicetype.h | 1 + 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/source/encoder/encoder.cpp b/source/encoder/encoder.cpp index 8f85244d1..29183aba2 100644 --- a/source/encoder/encoder.cpp +++ b/source/encoder/encoder.cpp @@ -4261,11 +4261,6 @@ void Encoder::configure(x265_param *p) p->limitReferences = 0; } - if ((p->bEnableTemporalFilter) && (p->bFrameAdaptive == 0)) { - x265_log(p, X265_LOG_WARNING, "MCSTF is unsupported when b-adapt = 0. Disabling MCSTF.\n"); - p->bEnableTemporalFilter = 0; - } - if ((p->bEnableTemporalFilter) && (p->bframes < 5)){ x265_log(p, X265_LOG_WARNING, "Setting the number of B-frames to 5, as MCSTF filter is enabled.\n"); p->bframes = 5; diff --git a/source/encoder/slicetype.cpp b/source/encoder/slicetype.cpp index 92b5923f8..14cd4be53 100644 --- a/source/encoder/slicetype.cpp +++ b/source/encoder/slicetype.cpp @@ -1024,6 +1024,14 @@ Lookahead::Lookahead(x265_param *param, ThreadPool* pool) * of work */ m_bBatchFrameCosts = m_bBatchMotionSearch; + /* MCSTF motion search is independent of --b-adapt: it reuses the batched + * CostEstimateGroup plumbing but is functionally decoupled from the frame + * cost DP. finishBatch() runs serially on the lookahead thread when no pool + * is present, so this needs neither a pool nor trellis b-adapt. Unlike + * m_bBatchMotionSearch it must NOT self-disable on small pools (MCSTF has no + * lazy fallback), so keep it a simple enable flag. */ + m_bMcstfMotionSearch = m_param->bEnableTemporalFilter; + if (m_param->lookaheadSlices && !m_pool) { x265_log(param, X265_LOG_WARNING, "No pools found; disabling lookahead-slices\n"); @@ -2154,7 +2162,7 @@ void Lookahead::slicetypeDecide() } } - if (m_bBatchMotionSearch && m_param->bEnableTemporalFilter) + if (m_bMcstfMotionSearch) { m_inputLock.acquire(); Frame* frameEnc = m_inputQueue.first(); @@ -2197,9 +2205,6 @@ void Lookahead::slicetypeDecide() frameEnc = frameEnc->m_next; } m_inputLock.release(); - - /* auto-disable after the first batch if pool is small */ - m_bBatchMotionSearch &= m_pool->m_numWorkers >= 4; } if (m_param->bEnableTemporalSubLayers > 2) diff --git a/source/encoder/slicetype.h b/source/encoder/slicetype.h index 2e6d565e1..d312b8732 100644 --- a/source/encoder/slicetype.h +++ b/source/encoder/slicetype.h @@ -183,6 +183,7 @@ class Lookahead : public JobProvider bool m_outputSignalRequired; bool m_bBatchMotionSearch; bool m_bBatchFrameCosts; + bool m_bMcstfMotionSearch; bool m_filled; bool m_isSceneTransition; int m_numPools; From c824ad9fe58c86c44cbb2f629c6c566f9c5ade49 Mon Sep 17 00:00:00 2001 From: Logaprakash Ramajayam Date: Mon, 6 Jul 2026 19:00:11 +0530 Subject: [PATCH 19/23] Code cleanup for MCSTF --- doc/reST/cli.rst | 2 -- source/common/lowres.cpp | 14 -------- source/common/lowres.h | 1 - source/common/param.cpp | 14 ++------ source/common/temporalfilter.cpp | 62 ++++++++++++++++---------------- source/common/temporalfilter.h | 27 +++++++------- source/encoder/frameencoder.cpp | 2 +- source/encoder/slicetype.cpp | 32 +++++++---------- source/encoder/slicetype.h | 5 ++- source/x265.h | 5 --- 10 files changed, 64 insertions(+), 100 deletions(-) diff --git a/doc/reST/cli.rst b/doc/reST/cli.rst index 4e24e01ea..617fed125 100755 --- a/doc/reST/cli.rst +++ b/doc/reST/cli.rst @@ -1366,8 +1366,6 @@ Temporal / motion search options combined by using adaptive filtering to produce a final noise-reduced picture. Default: disabled - Note : MCSTF should be enabled only with frame threads 1 - .. option:: --mcstf-ref-range Number of reference frames to consider on each side (past and future) of the diff --git a/source/common/lowres.cpp b/source/common/lowres.cpp index 856321069..5132b4717 100644 --- a/source/common/lowres.cpp +++ b/source/common/lowres.cpp @@ -200,11 +200,6 @@ bool Lowres::create(x265_param* param, PicYuv *origPic, uint32_t qgSize) } } - for (int i = 0; i < 4; i++) - { - CHECKED_MALLOC(lowresMcstfMvs[0][i], MV, cuCount); - } - for (int i = 0; i < bframes + 2; i++) { CHECKED_MALLOC(lowresMvs[0][i], MV, cuCount); @@ -293,10 +288,6 @@ void Lowres::destroy(x265_param* param) } } - for (int i = 0; i < 4; i++) - { - X265_FREE(lowresMcstfMvs[0][i]); - } X265_FREE(qpAqOffset); X265_FREE(invQscaleFactor); X265_FREE(qpCuTreeOffset); @@ -374,11 +365,6 @@ void Lowres::init(PicYuv* origPic, int poc, bool bEnableTemporalFilter) lowresMvs[1][i][0].x = 0x7FFF; } - for (int i = 0; i < 4; i++) - { - lowresMcstfMvs[0][i][0].x = 0x7FFF; - } - for (int i = 0; i < bframes + 2; i++) intraMbs[i] = 0; if (origPic->m_param->rc.vbvBufferSize) diff --git a/source/common/lowres.h b/source/common/lowres.h index 2d29830a8..d5d29bd46 100644 --- a/source/common/lowres.h +++ b/source/common/lowres.h @@ -192,7 +192,6 @@ struct Lowres : public ReferencePlanes uint16_t* lowresCosts[X265_BFRAME_MAX + 2][X265_BFRAME_MAX + 2]; int32_t* lowresMvCosts[2][X265_BFRAME_MAX + 2]; MV* lowresMvs[2][X265_BFRAME_MAX + 2]; - MV* lowresMcstfMvs[2][4]; uint32_t maxBlocksInRow; uint32_t maxBlocksInCol; uint32_t maxBlocksInRowFullRes; diff --git a/source/common/param.cpp b/source/common/param.cpp index fd6d33ab6..79e07a8da 100755 --- a/source/common/param.cpp +++ b/source/common/param.cpp @@ -418,10 +418,6 @@ void x265_param_default(x265_param* param) /* MCSTF */ param->bEnableTemporalFilter = 0; - param->temporalFilterStrength = 0.95; - param->searchRangeForLayer0 = 3; - param->searchRangeForLayer1 = 3; - param->searchRangeForLayer2 = 3; /* Threaded ME */ param->tmeTaskBlockSize = 1; @@ -1960,10 +1956,10 @@ int x265_check_params(x265_param* param) } } } - if (param->mcstfFrameRange > 8) + if (param->mcstfFrameRange > 4) { - param->mcstfFrameRange = 8; - x265_log(param, X265_LOG_WARNING, "MCSTF reference range should not exceed 8. Setting MCSTF reference range to 8\n"); + param->mcstfFrameRange = 4; + x265_log(param, X265_LOG_WARNING, "MCSTF reference range should not exceed 4. Setting MCSTF reference range to 4\n"); } if (param->bEnableHME) { @@ -3036,10 +3032,6 @@ void x265_copy_params(x265_param* dst, x265_param* src) } dst->bField = src->bField; dst->bEnableTemporalFilter = src->bEnableTemporalFilter; - dst->temporalFilterStrength = src->temporalFilterStrength; - dst->searchRangeForLayer0 = src->searchRangeForLayer0; - dst->searchRangeForLayer1 = src->searchRangeForLayer1; - dst->searchRangeForLayer2 = src->searchRangeForLayer2; dst->confWinRightOffset = src->confWinRightOffset; dst->confWinBottomOffset = src->confWinBottomOffset; dst->bliveVBV2pass = src->bliveVBV2pass; diff --git a/source/common/temporalfilter.cpp b/source/common/temporalfilter.cpp index 0260ff869..a99d81981 100644 --- a/source/common/temporalfilter.cpp +++ b/source/common/temporalfilter.cpp @@ -417,6 +417,7 @@ TemporalFilter::TemporalFilter() m_chromaFactor = 0.55; m_sigmaMultiplier = 9.0; m_sigmaZeroPoint = 10.0; + m_overallStrength = 0.95; } TemporalFilter::~TemporalFilter() @@ -461,8 +462,7 @@ int TemporalFilter::createRefPicInfo(TemporalFilterRefPicInfo* refFrame, x265_pa return 0; } -int MotionEstimatorTLD::motionErrorLumaSSD(MotionEstimatorTLD& m_metld, - pixel* src, +int MotionEstimatorTLD::motionErrorLumaSSD(pixel* src, int stride, pixel* buf, int x, @@ -503,7 +503,7 @@ int MotionEstimatorTLD::motionErrorLumaSSD(MotionEstimatorTLD& m_metld, /* copy PU block into cache */ primitives.pu[partEnum].copy_pp(predPUYuv.m_buf[0], FENC_STRIDE, bufferRowStart, buffStride); - error = (int)primitives.cu[partEnum].sse_pp(m_metld.me.fencPUYuv.m_buf[0], FENC_STRIDE, predPUYuv.m_buf[0], FENC_STRIDE); + error = (int)primitives.cu[partEnum].sse_pp(me.fencPUYuv.m_buf[0], FENC_STRIDE, predPUYuv.m_buf[0], FENC_STRIDE); #endif if (error > besterror) @@ -556,7 +556,7 @@ void TemporalFilter::applyMotion(MV *mvs, uint32_t mvsStride, PicYuv *input, Pic } } -void TemporalFilter::bilateralFilterCore(Frame* frame, TemporalFilterRefPicInfo* m_mcstfRefList, int numRefs, int blockRow, int rowSize, double overallStrength) +void TemporalFilter::bilateralFilterCore(Frame* frame, TemporalFilterRefPicInfo* m_mcstfRefList, int numRefs, int blockRow, int rowSize) { int refStrengthRow = 0; @@ -581,7 +581,7 @@ void TemporalFilter::bilateralFilterCore(Frame* frame, TemporalFilterRefPicInfo* const intptr_t srcStride = (!c) ? orgPic->m_stride : (intptr_t)orgPic->m_strideC; const double sigmaSq = (!c) ? lumaSigmaSq : chromaSigmaSq; - const double weightScaling = overallStrength * ((!c) ? 0.4 : m_chromaFactor); + const double weightScaling = m_overallStrength * ((!c) ? 0.4 : m_chromaFactor); const double maxSampleValue = (1 << m_bitDepth) - 1; const double bitDepthDiffWeighting = 1024.0 / (maxSampleValue + 1); const int blkSize = (!c) ? 8 : 4; @@ -599,7 +599,7 @@ void TemporalFilter::bilateralFilterCore(Frame* frame, TemporalFilterRefPicInfo* const pixel* srcPel = srcPelPlane + by * srcStride + bx; // Step 1: noise computation via SIMD primitive - double minError = INT64_MAX; + double minError = DBL_MAX; for (int i = 0; i < numRefs; i++) { TemporalFilterRefPicInfo* refPicInfo = &m_mcstfRefList[i]; @@ -659,7 +659,7 @@ void TemporalFilter::bilateralFilterCore(Frame* frame, TemporalFilterRefPicInfo* } // Splits the frame into 64-row blocks, dispatches jobs to the threadpool via BilateralFilterGroup, then returns. -void TemporalFilter::bilateralFilter(Frame* curFrame, TemporalFilterRefPicInfo* mcstfRefList, double overallStrength, ThreadPool* pool) +void TemporalFilter::bilateralFilter(Frame* curFrame, TemporalFilterRefPicInfo* mcstfRefList, ThreadPool* pool) { const int numRef = curFrame->m_mcstf->m_numRef; const int rowSize = 64; @@ -671,26 +671,26 @@ void TemporalFilter::bilateralFilter(Frame* curFrame, TemporalFilterRefPicInfo* if (!pool) { - bilateralFilterCore(curFrame, mcstfRefList, numRef, 0, 0, overallStrength); + bilateralFilterCore(curFrame, mcstfRefList, numRef, 0, 0); return; } BilateralFilterGroup filterGroup(*this, pool); for (int row = 0; row < numBlockRows; row++) - filterGroup.add(curFrame, mcstfRefList, numRef, row, rowSize, overallStrength); + filterGroup.add(curFrame, mcstfRefList, numRef, row, rowSize); filterGroup.finishBatch(); } -void MotionEstimatorTLD::motionEstimationLuma(MotionEstimatorTLD& m_metld, MV *mvs, uint32_t mvStride, pixel* src,int stride, int height, int width, pixel* buf, int blockSize, - int sRange, int row, int rowSize, MV* previous, uint32_t prevMvStride, int factor) +void MotionEstimatorTLD::motionEstimationLuma(MV *mvs, uint32_t mvStride, pixel* src,int stride, int height, int width, pixel* buf, + int row, const int rowSize, MV* previous, uint32_t prevMvStride, int factor) { - int range = sRange; + int range = m_searchRange; + int stepSize, blockSize; - - const int stepSize = blockSize; + stepSize = blockSize = m_blockSize; const int origWidth = width; const int origHeight = height; @@ -707,7 +707,7 @@ void MotionEstimatorTLD::motionEstimationLuma(MotionEstimatorTLD& m_metld, MV *m for (int blockX = 0; blockX + blockSize <= origWidth; blockX += stepSize) { const intptr_t pelOffset = blockY * stride + blockX; - m_metld.me.setSourcePU(src, stride, pelOffset, blockSize, blockSize, X265_HEX_SEARCH, 1); + me.setSourcePU(src, stride, pelOffset, blockSize, blockSize, X265_HEX_SEARCH, 1); MV best(0, 0); @@ -715,7 +715,7 @@ void MotionEstimatorTLD::motionEstimationLuma(MotionEstimatorTLD& m_metld, MV *m if (previous == NULL) { - range = sRange; + range = m_searchRange; } else { @@ -733,7 +733,7 @@ void MotionEstimatorTLD::motionEstimationLuma(MotionEstimatorTLD& m_metld, MV *m int mvIdx = testy * prevMvStride + testx; MV old = previous[mvIdx]; - error = motionErrorLumaSSD(m_metld, src, stride, buf, blockX, blockY, old.x * factor, old.y * factor, blockSize, leastError); + error = motionErrorLumaSSD(src, stride, buf, blockX, blockY, old.x * factor, old.y * factor, blockSize, leastError); if (error < leastError) { @@ -744,7 +744,7 @@ void MotionEstimatorTLD::motionEstimationLuma(MotionEstimatorTLD& m_metld, MV *m } } - error = motionErrorLumaSSD(m_metld, src, stride, buf, blockX, blockY, 0, 0, blockSize, leastError); + error = motionErrorLumaSSD(src, stride, buf, blockX, blockY, 0, 0, blockSize, leastError); if (error < leastError) { @@ -759,7 +759,7 @@ void MotionEstimatorTLD::motionEstimationLuma(MotionEstimatorTLD& m_metld, MV *m { for (int x2 = prevBest.x / m_motionVectorFactor - range; x2 <= prevBest.x / m_motionVectorFactor + range; x2++) { - error = motionErrorLumaSSD(m_metld, src, stride, buf, blockX, blockY, x2 * m_motionVectorFactor, y2 * m_motionVectorFactor, blockSize, leastError); + error = motionErrorLumaSSD(src, stride, buf, blockX, blockY, x2 * m_motionVectorFactor, y2 * m_motionVectorFactor, blockSize, leastError); if (error < leastError) { @@ -776,7 +776,7 @@ void MotionEstimatorTLD::motionEstimationLuma(MotionEstimatorTLD& m_metld, MV *m int idx = ((blockY / stepSize) * mvStride + (blockX - stepSize) / stepSize); MV leftMV = mvs[idx]; - error = motionErrorLumaSSD(m_metld, src, stride, buf, blockX, blockY, leftMV.x, leftMV.y, blockSize, leastError); + error = motionErrorLumaSSD(src, stride, buf, blockX, blockY, leftMV.x, leftMV.y, blockSize, leastError); if (error < leastError) { @@ -792,14 +792,14 @@ void MotionEstimatorTLD::motionEstimationLuma(MotionEstimatorTLD& m_metld, MV *m } -void MotionEstimatorTLD::motionEstimationLumaDoubleRes(MotionEstimatorTLD& m_metld, MV *mvs, uint32_t mvStride, PicYuv *orig, PicYuv *buffer, int blockSize, - MV *previous, uint32_t prevMvStride, int factor, int* minError, int row, int rowSize) +void MotionEstimatorTLD::motionEstimationLumaDoubleRes(MV *mvs, uint32_t mvStride, PicYuv *orig, PicYuv *buffer, + MV *previous, uint32_t prevMvStride, int factor, int* minError, int row, const int rowSize) { int range = 0; + int stepSize, blockSize; - - const int stepSize = blockSize; + stepSize = blockSize = m_blockSize / 2; const int origWidth = orig->m_picWidth; const int origHeight = orig->m_picHeight; @@ -818,7 +818,7 @@ void MotionEstimatorTLD::motionEstimationLumaDoubleRes(MotionEstimatorTLD& m_met { const intptr_t pelOffset = blockY * orig->m_stride + blockX; - m_metld.me.setSourcePU(orig->m_picOrg[0], orig->m_stride, pelOffset, blockSize, blockSize, X265_HEX_SEARCH, 1); + me.setSourcePU(orig->m_picOrg[0], orig->m_stride, pelOffset, blockSize, blockSize, X265_HEX_SEARCH, 1); MV best(0, 0); int leastError = INT_MAX; @@ -843,7 +843,7 @@ void MotionEstimatorTLD::motionEstimationLumaDoubleRes(MotionEstimatorTLD& m_met int mvIdx = testy * prevMvStride + testx; MV old = previous[mvIdx]; - error = motionErrorLumaSSD(m_metld, orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, old.x * factor, old.y * factor, blockSize, leastError); + error = motionErrorLumaSSD(orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, old.x * factor, old.y * factor, blockSize, leastError); if (error < leastError) { @@ -854,7 +854,7 @@ void MotionEstimatorTLD::motionEstimationLumaDoubleRes(MotionEstimatorTLD& m_met } } - error = motionErrorLumaSSD(m_metld, orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, 0, 0, blockSize, leastError); + error = motionErrorLumaSSD(orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, 0, 0, blockSize, leastError); if (error < leastError) { @@ -869,7 +869,7 @@ void MotionEstimatorTLD::motionEstimationLumaDoubleRes(MotionEstimatorTLD& m_met { for (int x2 = prevBest.x / m_motionVectorFactor - range; x2 <= prevBest.x / m_motionVectorFactor + range; x2++) { - error = motionErrorLumaSSD(m_metld, orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, x2 * m_motionVectorFactor, y2 * m_motionVectorFactor, blockSize, leastError); + error = motionErrorLumaSSD(orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, x2 * m_motionVectorFactor, y2 * m_motionVectorFactor, blockSize, leastError); if (error < leastError) { @@ -885,7 +885,7 @@ void MotionEstimatorTLD::motionEstimationLumaDoubleRes(MotionEstimatorTLD& m_met { for (int x2 = prevBest.x - doubleRange; x2 <= prevBest.x + doubleRange; x2++) { - error = motionErrorLumaSSD(m_metld, orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, x2, y2, blockSize, leastError); + error = motionErrorLumaSSD(orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, x2, y2, blockSize, leastError); if (error < leastError) { @@ -901,7 +901,7 @@ void MotionEstimatorTLD::motionEstimationLumaDoubleRes(MotionEstimatorTLD& m_met int idx = ((blockY - stepSize) / stepSize) * mvStride + (blockX / stepSize); MV aboveMV = mvs[idx]; - error = motionErrorLumaSSD(m_metld, orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, aboveMV.x, aboveMV.y, blockSize, leastError); + error = motionErrorLumaSSD(orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, aboveMV.x, aboveMV.y, blockSize, leastError); if (error < leastError) { @@ -915,7 +915,7 @@ void MotionEstimatorTLD::motionEstimationLumaDoubleRes(MotionEstimatorTLD& m_met int idx = ((blockY / stepSize) * mvStride + (blockX - stepSize) / stepSize); MV leftMV = mvs[idx]; - error = motionErrorLumaSSD(m_metld, orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, leftMV.x, leftMV.y, blockSize, leastError); + error = motionErrorLumaSSD(orig->m_picOrg[0], (int)orig->m_stride, buffer->m_picOrg[0], blockX, blockY, leftMV.x, leftMV.y, blockSize, leastError); if (error < leastError) { diff --git a/source/common/temporalfilter.h b/source/common/temporalfilter.h index 74c286871..f26d7041c 100644 --- a/source/common/temporalfilter.h +++ b/source/common/temporalfilter.h @@ -31,6 +31,7 @@ #include "yuv.h" #include "motion.h" #include "threadpool.h" +#include const int s_interpolationFilter[16][8] = { @@ -119,24 +120,26 @@ namespace X265_NS { me.init(X265_CSP_I400); me.setQP(X265_LOOKAHEAD_QP); predPUYuv.create(FENC_STRIDE, X265_CSP_I400); - m_useSADinME = 0; m_motionVectorFactor = 16; + m_searchRange = 3; + m_blockSize = 16; } Yuv predPUYuv; - int m_useSADinME; int m_motionVectorFactor; int32_t m_bitDepth; + int m_searchRange; + int m_blockSize; void init(const x265_param* param); - void motionEstimationLuma(MotionEstimatorTLD& m_tld, MV* mvs, uint32_t mvStride, pixel* src, int stride, int height, int width, pixel* buf, int bs, int sRange, int row, int rowSize, + void motionEstimationLuma(MV* mvs, uint32_t mvStride, pixel* src, int stride, int height, int width, pixel* buf, int row, const int rowSize, MV* previous = 0, uint32_t prevmvStride = 0, int factor = 1); - void motionEstimationLumaDoubleRes(MotionEstimatorTLD& m_tld, MV* mvs, uint32_t mvStride, PicYuv* orig, PicYuv* buffer, int blockSize, - MV* previous, uint32_t prevMvStride, int factor, int* minError, int row, int rowSize); + void motionEstimationLumaDoubleRes(MV* mvs, uint32_t mvStride, PicYuv* orig, PicYuv* buffer, + MV* previous, uint32_t prevMvStride, int factor, int* minError, int row, const int rowSize); - int motionErrorLumaSSD(MotionEstimatorTLD& m_tld, pixel* src, + int motionErrorLumaSSD(pixel* src, int stride, pixel* buf, int x, @@ -209,20 +212,19 @@ namespace X265_NS { uint8_t m_sliceTypeConfig; MotionEstimatorTLD* m_metld; + double m_overallStrength; int createRefPicInfo(TemporalFilterRefPicInfo* refFrame, x265_param* param); void bilateralFilter(Frame* frame, TemporalFilterRefPicInfo* mcstfRefList, - double overallStrength, ThreadPool* pool); void bilateralFilterCore(Frame* frame, TemporalFilterRefPicInfo* mcstfRefList, int numRef, int blockRow, - int blockSize, - double overallStrength); + int blockSize); void destroyRefPicInfo(TemporalFilterRefPicInfo* curFrame); @@ -243,7 +245,6 @@ namespace X265_NS { int numRef; int blockRow; int rowSize; - double overallStrength; }; static const int MAX_FILTER_JOBS = 512; @@ -256,7 +257,7 @@ namespace X265_NS { : m_filter(f), m_pool(pool), m_jobTotal(0), m_jobAcquired(0) {} void add(Frame* frame, TemporalFilterRefPicInfo* mcstfRefList, - int numRef, int blockRow, int rowSize, double strength) + int numRef, int blockRow, int rowSize) { X265_CHECK(m_jobTotal < MAX_FILTER_JOBS, "BilateralFilterGroup overflow\n"); @@ -266,7 +267,6 @@ namespace X265_NS { j.numRef = numRef; j.blockRow = blockRow; j.rowSize = rowSize; - j.overallStrength = strength; } void finishBatch() @@ -288,8 +288,7 @@ namespace X265_NS { const FilterJob& j = m_jobs[i]; m_filter.bilateralFilterCore(j.frame, j.mcstfRefList, j.numRef, - j.blockRow, j.rowSize, - j.overallStrength); + j.blockRow, j.rowSize); m_lock.acquire(); } m_lock.release(); diff --git a/source/encoder/frameencoder.cpp b/source/encoder/frameencoder.cpp index f02fb9a8a..1a9f52bb0 100644 --- a/source/encoder/frameencoder.cpp +++ b/source/encoder/frameencoder.cpp @@ -663,7 +663,7 @@ void FrameEncoder::compressFrame(int layer) if (m_param->bEnableTemporalFilter && m_top->isFilterThisframe(m_frame[layer]->m_mcstf->m_sliceTypeConfig, m_frame[layer]->m_lowres.sliceType)) { m_frame[layer]->m_mcstf->m_QP = qp; - m_frame[layer]->m_mcstf->bilateralFilter(m_frame[layer], m_frame[layer]->m_mcstfRefList, m_param->temporalFilterStrength, m_pool); + m_frame[layer]->m_mcstf->bilateralFilter(m_frame[layer], m_frame[layer]->m_mcstfRefList, m_pool); } if (m_nr) diff --git a/source/encoder/slicetype.cpp b/source/encoder/slicetype.cpp index 14cd4be53..689d9ae7f 100644 --- a/source/encoder/slicetype.cpp +++ b/source/encoder/slicetype.cpp @@ -2178,11 +2178,10 @@ void Lookahead::slicetypeDecide() ProfileLookaheadTimeCount(m_temporalFilterElapsedTime, m_countTemporalFilter); - const int rowMELevels = 4; - const int rowSize = 16; + const int rowSize = PARALLEL_ME_ROWSIZE; const int origHeight = frameEnc->m_fencPic->m_picHeight; - const int levelHeight[4] = {origHeight / 4, origHeight / 2, origHeight, origHeight}; - for(int i = 0; i < rowMELevels; i++) + const int levelHeight[4] = {origHeight >> 2, origHeight >> 1, origHeight, origHeight}; + for(int i = 0; i < MOTION_ESTIMATION_LEVELS; i++) { const int numBlockRows = (levelHeight[i] + rowSize - 1) / rowSize; CostEstimateGroup estGroup(*this, frames); @@ -2192,12 +2191,8 @@ void Lookahead::slicetypeDecide() TemporalFilterRefPicInfo* ref = &frameEnc->m_mcstfRefList[j - 1]; int refpoc = ref->poc; - /* Skip search if already done */ - if (frameEnc->m_lowres.lowresMcstfMvs[0][j - 1][0].x != 0x7FFF) - continue; - for (int row = 0; row < numBlockRows; row++) - estGroup.add_row(j - 1, refpoc, frameEnc->m_poc, row, i, frameEnc); + estGroup.addRow(j - 1, refpoc, frameEnc->m_poc, row, i, frameEnc); } estGroup.finishBatch(); } @@ -4074,7 +4069,7 @@ void CostEstimateGroup::add(int p0, int p1, int b) finishBatch(); } -void CostEstimateGroup::add_row(int refIdx, int poc, int curPoc, int blockRow, int level, Frame* frame) +void CostEstimateGroup::addRow(int refIdx, int poc, int curPoc, int blockRow, int level, Frame* frame) { X265_CHECK(m_batchMode || !m_jobTotal, "single CostEstimateGroup instance cannot mix batch modes\n"); @@ -4121,25 +4116,22 @@ void CostEstimateGroup::processTasks(int workerThreadID) ProfileScopeEvent(estCostSingle); Estimate& e = m_estimates[i]; Frame* curFrame = e.frame; - if (m_lookahead.m_param->bEnableTemporalFilter && curFrame && (curFrame->m_lowres.sliceType == X265_TYPE_IDR || curFrame->m_lowres.sliceType == X265_TYPE_I || curFrame->m_lowres.sliceType == X265_TYPE_P)) + if (m_lookahead.m_param->bEnableTemporalFilter && curFrame && m_lookahead.isFilterThisframe(curFrame->m_mcstf->m_sliceTypeConfig, curFrame->m_lowres.sliceType)) { ProfileLookaheadTime(tld.mcstfBatchElapsedTime); m_metld.m_bitDepth = curFrame->m_param->internalBitDepth; TemporalFilterRefPicInfo* ref = &curFrame->m_mcstfRefList[e.p0]; - int rowSize = 16; + Lowres *lowres = &curFrame->m_lowres; + const int rowSize = PARALLEL_ME_ROWSIZE; if (e.MElevel == 0) - m_metld.motionEstimationLuma(m_metld, ref->mvs0, ref->mvsStride0, curFrame->m_lowres.lowerResPlane[0], (int)(curFrame->m_lowres.lumaStride / 2), (curFrame->m_lowres.lines / 2), (curFrame->m_lowres.width / 2), ref->lowerRes, 16, curFrame->m_param->searchRangeForLayer2, e.blockRow, rowSize); + m_metld.motionEstimationLuma(ref->mvs0, ref->mvsStride0, lowres->lowerResPlane[0], (int)(lowres->lumaStride / 2), (lowres->lines / 2), (lowres->width / 2), ref->lowerRes, e.blockRow, rowSize); else if (e.MElevel == 1) - m_metld.motionEstimationLuma(m_metld, ref->mvs1, ref->mvsStride1, curFrame->m_lowres.lowresPlane[0], (int)(curFrame->m_lowres.lumaStride), (curFrame->m_lowres.lines), (curFrame->m_lowres.width), ref->lowres, 16, curFrame->m_param->searchRangeForLayer1, e.blockRow, rowSize, ref->mvs0, ref->mvsStride0, 2); + m_metld.motionEstimationLuma(ref->mvs1, ref->mvsStride1, lowres->lowresPlane[0], (int)(lowres->lumaStride), (lowres->lines), (lowres->width), ref->lowres, e.blockRow, rowSize, ref->mvs0, ref->mvsStride0, 2); else if (e.MElevel == 2) - m_metld.motionEstimationLuma(m_metld, ref->mvs2, ref->mvsStride2, curFrame->m_fencPic->m_picOrg[0], (int)curFrame->m_fencPic->m_stride, curFrame->m_fencPic->m_picHeight, curFrame->m_fencPic->m_picWidth, ref->picBuffer->m_picOrg[0], 16, curFrame->m_param->searchRangeForLayer0, e.blockRow, rowSize, ref->mvs1, ref->mvsStride1, 2); + m_metld.motionEstimationLuma(ref->mvs2, ref->mvsStride2, curFrame->m_fencPic->m_picOrg[0], (int)curFrame->m_fencPic->m_stride, curFrame->m_fencPic->m_picHeight, curFrame->m_fencPic->m_picWidth, ref->picBuffer->m_picOrg[0], e.blockRow, rowSize, ref->mvs1, ref->mvsStride1, 2); else if (e.MElevel == 3) - { - m_metld.motionEstimationLumaDoubleRes(m_metld, ref->mvs, ref->mvsStride, curFrame->m_fencPic, ref->picBuffer, 8, ref->mvs2, ref->mvsStride2, 1, ref->error, e.blockRow, rowSize); - if (e.blockRow == (int)(curFrame->m_fencPic->m_picHeight + (rowSize - 1))/rowSize - 1) - curFrame->m_lowres.lowresMcstfMvs[0][e.p0][0].x = 1; - } + m_metld.motionEstimationLumaDoubleRes(ref->mvs, ref->mvsStride, curFrame->m_fencPic, ref->picBuffer, ref->mvs2, ref->mvsStride2, 1, ref->error, e.blockRow, rowSize); } else { diff --git a/source/encoder/slicetype.h b/source/encoder/slicetype.h index d312b8732..373b95dd2 100644 --- a/source/encoder/slicetype.h +++ b/source/encoder/slicetype.h @@ -63,6 +63,9 @@ class Lookahead; #define NUM64x64INPIC(w,h) ((w*h)>> (MAX_LOG2_CU_SIZE<<1)) +#define MOTION_ESTIMATION_LEVELS 4 +#define PARALLEL_ME_ROWSIZE 16 + #if HIGH_BIT_DEPTH #define EDGE_THRESHOLD 1023.0 #else @@ -322,7 +325,7 @@ class CostEstimateGroup : public BondedTaskGroup } m_estimates[MAX_BATCH_SIZE]; void add(int p0, int p1, int b); - void add_row(int refIdx, int poc, int curPoc, int blockRow, int level, Frame* frame); + void addRow(int refIdx, int poc, int curPoc, int blockRow, int level, Frame* frame); void finishBatch(); protected: diff --git a/source/x265.h b/source/x265.h index a3b8800a9..d07826cf4 100644 --- a/source/x265.h +++ b/source/x265.h @@ -2338,11 +2338,6 @@ typedef struct x265_param /*Motion compensated temporal filter*/ int bEnableTemporalFilter; - double temporalFilterStrength; - /*Search Range for L0, L1 and L2 in MCSTF*/ - int searchRangeForLayer0; - int searchRangeForLayer1; - int searchRangeForLayer2; /* Threaded ME */ /* Number of CTUs processed at once when a worker thread picks up a task from ThreadedME. */ From faaae542187d9d479f0e3a667957007cc75ad681 Mon Sep 17 00:00:00 2001 From: Kirithika7 Date: Mon, 20 Jul 2026 15:07:44 +0530 Subject: [PATCH 20/23] mcstf: Add --selective-mcstf CLI option to enable noise-based MCSTF filtering --- doc/reST/cli.rst | 14 ++- source/CMakeLists.txt | 2 +- source/common/lowres.cpp | 2 + source/common/lowres.h | 2 + source/common/param.cpp | 7 ++ source/encoder/api.cpp | 9 ++ source/encoder/encoder.cpp | 9 ++ source/encoder/frameencoder.cpp | 2 +- source/encoder/slicetype.cpp | 168 ++++++++++++++++++++++++-------- source/encoder/slicetype.h | 5 +- source/x265.h | 7 +- source/x265cli.cpp | 1 + source/x265cli.h | 2 + 13 files changed, 187 insertions(+), 43 deletions(-) diff --git a/doc/reST/cli.rst b/doc/reST/cli.rst index 617fed125..4494099c5 100755 --- a/doc/reST/cli.rst +++ b/doc/reST/cli.rst @@ -1358,12 +1358,15 @@ Temporal / motion search options Motion-compensated spatio-temporal filtering (MCSTF) improves the compression efficiency of videos that contain a high level of noise. It introduces a - temporal filter before encoding and this filter is applied only to the I- and P-frames. + temporal filter before encoding, and this filter is applied unconditionally + to every I- and P-frame regardless of how noisy the source content actually is. It utilizes previously generated motion vectors across different video content resolutions to find the best temporal correspondence for low-pass filtering. Here, motion estimation is applied between the central picture and each future or past picture, thereby generating multiple motion-compensated predictions, which are then combined by using adaptive filtering to produce a final noise-reduced picture. + To filter frames selectively based on their estimated noise level instead, use + :option:`--selective-mcstf`. Default: disabled .. option:: --mcstf-ref-range @@ -1374,6 +1377,15 @@ Temporal / motion search options at the cost of increased memory usage and processing time. The valid range is 1 to 4. Default: 2 +.. option:: --selective-mcstf, --no-selective-mcstf + + Gate MCSTF with a per-GOP noise estimate: skip the filter for GOPs whose + starting I/IDR frame (or scenecut) is estimated as clean (low noise), and + apply it only where it helps. Implicitly enables :option:`--mcstf` if it + was not already turned on. When :option:`--csv-log-level` is 2 or higher, + adds a Frame Noise and IsMCSTFEnabled column to the per-frame CSV log. + Default: disabled + Spatial/intra options ===================== diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index c9380d301..3177af99a 100755 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -32,7 +32,7 @@ option(NATIVE_BUILD "Target the build CPU" OFF) option(STATIC_LINK_CRT "Statically link C and C++ runtimes for release builds" OFF) mark_as_advanced(FPROFILE_USE FPROFILE_GENERATE NATIVE_BUILD) # X265_BUILD must be incremented each time the public API is changed -set(X265_BUILD 216) +set(X265_BUILD 217) configure_file("${PROJECT_SOURCE_DIR}/x265.def.in" "${PROJECT_BINARY_DIR}/x265.def") configure_file("${PROJECT_SOURCE_DIR}/x265_config.h.in" diff --git a/source/common/lowres.cpp b/source/common/lowres.cpp index 5132b4717..b15dc194d 100644 --- a/source/common/lowres.cpp +++ b/source/common/lowres.cpp @@ -346,6 +346,8 @@ void Lowres::init(PicYuv* origPic, int poc, bool bEnableTemporalFilter) bLastMiniGopBFrame = false; bKeyframe = false; // Not a keyframe unless identified by lookahead bIsFadeEnd = false; + filterThisGOP = false; + noiseScore = -1; frameNum = poc; leadingBframes = 0; indB = 0; diff --git a/source/common/lowres.h b/source/common/lowres.h index d5d29bd46..fa64ea4f5 100644 --- a/source/common/lowres.h +++ b/source/common/lowres.h @@ -177,6 +177,8 @@ struct Lowres : public ReferencePlanes bool bKeyframe; bool bLastMiniGopBFrame; bool bIsFadeEnd; + bool filterThisGOP; // Set by lookahead noise gate: true = GOP start was noisy, apply MCSTF + int32_t noiseScore; // Raw noise score from estimateNoise() at GOP start; -1 for non-GOP-start frames double ipCostRatio; diff --git a/source/common/param.cpp b/source/common/param.cpp index 79e07a8da..88d4069a7 100755 --- a/source/common/param.cpp +++ b/source/common/param.cpp @@ -418,6 +418,7 @@ void x265_param_default(x265_param* param) /* MCSTF */ param->bEnableTemporalFilter = 0; + param->bSelectiveMCSTF = 0; /* Threaded ME */ param->tmeTaskBlockSize = 1; @@ -1497,6 +1498,7 @@ int x265_param_parse(x265_param* p, const char* name, const char* value) OPT("aom-film-grain") p->aomFilmGrain = (char*)value; OPT("mcstf") p->bEnableTemporalFilter = atobool(value); OPT("mcstf-ref-range") p->mcstfFrameRange = atoi(value); + OPT("selective-mcstf") p->bSelectiveMCSTF = atobool(value); OPT("sbrc") p->bEnableSBRC = atobool(value); #if ENABLE_ALPHA OPT("alpha") @@ -2234,6 +2236,7 @@ void x265_print_params(x265_param* param) { TOOLOPT(param->bEnableTemporalFilter, "mcstf"); TOOLVAL(param->mcstfFrameRange, "mcstf-ref-range=%d"); + TOOLOPT(param->bSelectiveMCSTF, "selective-mcstf"); } x265_log(param, X265_LOG_INFO, "tools:%s\n", buf); fflush(stderr); @@ -2499,7 +2502,10 @@ char *x265_param2string(x265_param* p, int padx, int pady) s += snprintf(s, bufSize - (s - buf), " aom-film-grain=%s", p->aomFilmGrain); BOOL(p->bEnableTemporalFilter, "mcstf"); if (p->bEnableTemporalFilter) + { s += snprintf(s, bufSize - (s - buf), " mcstf-ref-range=%d", p->mcstfFrameRange); + BOOL(p->bSelectiveMCSTF, "selective-mcstf"); + } #if ENABLE_ALPHA BOOL(p->bEnableAlpha, "alpha"); #endif @@ -2734,6 +2740,7 @@ bool parseMaskingStrength(x265_param* p, const char* value) void x265_copy_params(x265_param* dst, x265_param* src) { dst->mcstfFrameRange = src->mcstfFrameRange; + dst->bSelectiveMCSTF = src->bSelectiveMCSTF; dst->cpuid = src->cpuid; dst->frameNumThreads = src->frameNumThreads; if (strlen(src->numaPools)) snprintf(dst->numaPools, X265_MAX_STRING_SIZE, "%s", src->numaPools); diff --git a/source/encoder/api.cpp b/source/encoder/api.cpp index 9e3e63291..efcc6bdb6 100644 --- a/source/encoder/api.cpp +++ b/source/encoder/api.cpp @@ -1417,6 +1417,8 @@ FILE* x265_csvlog_open(const x265_param* param) else if (param->rc.rateControlMode == X265_RC_CQP) fprintf(csvfp, ", Target QP"); } + if (param->bSelectiveMCSTF) + fprintf(csvfp, ", Frame Noise, IsMCSTFEnabled"); } fprintf(csvfp, "\n"); } @@ -1556,6 +1558,13 @@ void x265_csvlog_frame(const x265_param* param, const x265_picture* pic) else if (param->rc.rateControlMode == X265_RC_CQP) fprintf(param->csvfpt, ", %d", frameStats->currTrQP); } + if (param->bSelectiveMCSTF) + { + if (frameStats->frameNoise >= 0) + fprintf(param->csvfpt, ", %d, %d", frameStats->frameNoise, frameStats->isMCSTFEnabled); + else + fprintf(param->csvfpt, ", -, %d", frameStats->isMCSTFEnabled); + } } fprintf(param->csvfpt, "\n"); fflush(stderr); diff --git a/source/encoder/encoder.cpp b/source/encoder/encoder.cpp index 29183aba2..171efd6e6 100644 --- a/source/encoder/encoder.cpp +++ b/source/encoder/encoder.cpp @@ -3326,6 +3326,12 @@ void Encoder::finishFrameStats(Frame* curFrame, FrameEncoder *curEncoder, x265_f frameStats->currTrQP = curFrame->m_targetQp; } + if (m_param->bSelectiveMCSTF && m_param->csvLogLevel >= 2) + { + frameStats->frameNoise = curFrame->m_lowres.noiseScore; + frameStats->isMCSTFEnabled = curFrame->m_lowres.filterThisGOP ? 1 : 0; + } + if (m_param->csvLogLevel >= 1) { frameStats->cuStats.percentIntraNxN = curFrame->m_encData->m_frameStats.percentIntraNxN; @@ -4261,6 +4267,9 @@ void Encoder::configure(x265_param *p) p->limitReferences = 0; } + if (p->bSelectiveMCSTF && !p->bEnableTemporalFilter) + p->bEnableTemporalFilter = 1; + if ((p->bEnableTemporalFilter) && (p->bframes < 5)){ x265_log(p, X265_LOG_WARNING, "Setting the number of B-frames to 5, as MCSTF filter is enabled.\n"); p->bframes = 5; diff --git a/source/encoder/frameencoder.cpp b/source/encoder/frameencoder.cpp index 1a9f52bb0..5b64950b4 100644 --- a/source/encoder/frameencoder.cpp +++ b/source/encoder/frameencoder.cpp @@ -660,7 +660,7 @@ void FrameEncoder::compressFrame(int layer) } } } - if (m_param->bEnableTemporalFilter && m_top->isFilterThisframe(m_frame[layer]->m_mcstf->m_sliceTypeConfig, m_frame[layer]->m_lowres.sliceType)) + if (m_param->bEnableTemporalFilter && m_top->isFilterThisframe(m_frame[layer]->m_mcstf->m_sliceTypeConfig, m_frame[layer]->m_lowres.sliceType) && m_frame[layer]->m_lowres.filterThisGOP) { m_frame[layer]->m_mcstf->m_QP = qp; m_frame[layer]->m_mcstf->bilateralFilter(m_frame[layer], m_frame[layer]->m_mcstfRefList, m_pool); diff --git a/source/encoder/slicetype.cpp b/source/encoder/slicetype.cpp index 689d9ae7f..be8dfec37 100644 --- a/source/encoder/slicetype.cpp +++ b/source/encoder/slicetype.cpp @@ -97,14 +97,14 @@ uint32_t acEnergyVarHist(uint64_t sum_ssd, int shift) return ssd - ((uint64_t)sum * sum >> shift); } -bool computeEdge(pixel* edgePic, pixel* refPic, pixel* edgeTheta, intptr_t stride, int height, int width, bool bcalcTheta, pixel whitePixel) +bool computeEdge(pixel* edgePic, pixel* refPic, pixel* edgeTheta, intptr_t stride, int height, int width, bool bcalcTheta, pixel whitePixel, int32_t* gradMag) { intptr_t rowOne = 0, rowTwo = 0, rowThree = 0, colOne = 0, colTwo = 0, colThree = 0; intptr_t middle = 0, topLeft = 0, topRight = 0, bottomLeft = 0, bottomRight = 0; const int startIndex = 1; - if (!edgePic || !refPic || (!edgeTheta && bcalcTheta)) + if ((!edgePic && !gradMag) || !refPic || (!edgeTheta && bcalcTheta)) { return false; } @@ -142,7 +142,7 @@ bool computeEdge(pixel* edgePic, pixel* refPic, pixel* edgeTheta, intptr_t strid gradientH = (float)(-3 * refPic[topLeft] + 3 * refPic[topRight] - 10 * refPic[rowTwo + colOne] + 10 * refPic[rowTwo + colThree] - 3 * refPic[bottomLeft] + 3 * refPic[bottomRight]); gradientV = (float)(-3 * refPic[topLeft] - 10 * refPic[rowOne + colTwo] - 3 * refPic[topRight] + 3 * refPic[bottomLeft] + 10 * refPic[rowThree + colTwo] + 3 * refPic[bottomRight]); gradientMagnitude = sqrtf(gradientH * gradientH + gradientV * gradientV); - if(bcalcTheta) + if(bcalcTheta) { edgeTheta[middle] = 0; radians = atan2(gradientV, gradientH); @@ -151,13 +151,43 @@ bool computeEdge(pixel* edgePic, pixel* refPic, pixel* edgeTheta, intptr_t strid theta = 180 + theta; edgeTheta[middle] = (pixel)theta; } - edgePic[middle] = (pixel)(gradientMagnitude >= EDGE_THRESHOLD ? whitePixel : blackPixel); + if (edgePic) + edgePic[middle] = (pixel)(gradientMagnitude >= EDGE_THRESHOLD ? whitePixel : blackPixel); + if (gradMag) + gradMag[middle] = (int32_t)gradientMagnitude; } } return true; } } +static void gaussianBlur5x5(pixel* dst, const pixel* src, intptr_t stride, int height, int width) +{ + /* 5x5 Gaussian filter (sum = 159) + [2 4 5 4 2] + 1 [4 9 12 9 4] + --- [5 12 15 12 5] + 159 [4 9 12 9 4] + [2 4 5 4 2] */ + for (int rowNum = 2; rowNum < height - 2; rowNum++) + { + for (int colNum = 2; colNum < width - 2; colNum++) + { + const intptr_t rowOne = (rowNum - 2) * stride, colOne = colNum - 2; + const intptr_t rowTwo = (rowNum - 1) * stride, colTwo = colNum - 1; + const intptr_t rowThree = rowNum * stride, colThree = colNum; + const intptr_t rowFour = (rowNum + 1) * stride, colFour = colNum + 1; + const intptr_t rowFive = (rowNum + 2) * stride, colFive = colNum + 2; + dst[rowThree + colThree] = (pixel)(( + 2 * src[rowOne + colOne] + 4 * src[rowOne + colTwo] + 5 * src[rowOne + colThree] + 4 * src[rowOne + colFour] + 2 * src[rowOne + colFive] + + 4 * src[rowTwo + colOne] + 9 * src[rowTwo + colTwo] + 12 * src[rowTwo + colThree] + 9 * src[rowTwo + colFour] + 4 * src[rowTwo + colFive] + + 5 * src[rowThree + colOne] + 12 * src[rowThree + colTwo] + 15 * src[rowThree + colThree] + 12 * src[rowThree + colFour] + 5 * src[rowThree + colFive] + + 4 * src[rowFour + colOne] + 9 * src[rowFour + colTwo] + 12 * src[rowFour + colThree] + 9 * src[rowFour + colFour] + 4 * src[rowFour + colFive] + + 2 * src[rowFive + colOne] + 4 * src[rowFive + colTwo] + 5 * src[rowFive + colThree] + 4 * src[rowFive + colFour] + 2 * src[rowFive + colFive]) / 159); + } + } +} + void edgeFilter(Frame *curFrame, x265_param* param) { int height = curFrame->m_fencPic->m_picHeight; @@ -188,37 +218,7 @@ void edgeFilter(Frame *curFrame, x265_param* param) src = (pixel*)curFrame->m_fencPic->m_picOrg[0]; refPic = curFrame->m_gaussianPic + curFrame->m_fencPic->m_lumaMarginY * stride + curFrame->m_fencPic->m_lumaMarginX; edgePic = curFrame->m_edgePic + curFrame->m_fencPic->m_lumaMarginY * stride + curFrame->m_fencPic->m_lumaMarginX; - pixel pixelValue = 0; - - for (int rowNum = 0; rowNum < height; rowNum++) - { - for (int colNum = 0; colNum < width; colNum++) - { - if ((rowNum >= 2) && (colNum >= 2) && (rowNum < height - 2) && (colNum < width - 2)) //Ignoring the border pixels of the picture - { - /* 5x5 Gaussian filter - [2 4 5 4 2] - 1 [4 9 12 9 4] - --- [5 12 15 12 5] - 159 [4 9 12 9 4] - [2 4 5 4 2]*/ - - const intptr_t rowOne = (rowNum - 2)*stride, colOne = colNum - 2; - const intptr_t rowTwo = (rowNum - 1)*stride, colTwo = colNum - 1; - const intptr_t rowThree = rowNum * stride, colThree = colNum; - const intptr_t rowFour = (rowNum + 1)*stride, colFour = colNum + 1; - const intptr_t rowFive = (rowNum + 2)*stride, colFive = colNum + 2; - const intptr_t index = (rowNum*stride) + colNum; - - pixelValue = ((2 * src[rowOne + colOne] + 4 * src[rowOne + colTwo] + 5 * src[rowOne + colThree] + 4 * src[rowOne + colFour] + 2 * src[rowOne + colFive] + - 4 * src[rowTwo + colOne] + 9 * src[rowTwo + colTwo] + 12 * src[rowTwo + colThree] + 9 * src[rowTwo + colFour] + 4 * src[rowTwo + colFive] + - 5 * src[rowThree + colOne] + 12 * src[rowThree + colTwo] + 15 * src[rowThree + colThree] + 12 * src[rowThree + colFour] + 5 * src[rowThree + colFive] + - 4 * src[rowFour + colOne] + 9 * src[rowFour + colTwo] + 12 * src[rowFour + colThree] + 9 * src[rowFour + colFour] + 4 * src[rowFour + colFive] + - 2 * src[rowFive + colOne] + 4 * src[rowFive + colTwo] + 5 * src[rowFive + colThree] + 4 * src[rowFive + colFour] + 2 * src[rowFive + colFive]) / 159); - refPic[index] = pixelValue; - } - } - } + gaussianBlur5x5(refPic, src, stride, height, width); if(!computeEdge(edgePic, refPic, edgeTheta, stride, height, width, true)) x265_log(NULL, X265_LOG_ERROR, "Failed edge computation!"); @@ -970,6 +970,73 @@ void LookaheadTLD::weightsAnalyse(Lowres& fenc, Lowres& ref) } } +int32_t Lookahead::estimateNoise(Frame* curFrame) +{ + int width = curFrame->m_fencPic->m_picWidth; + int height = curFrame->m_fencPic->m_picHeight; + intptr_t stride = curFrame->m_fencPic->m_stride; + pixel* src = curFrame->m_fencPic->m_picOrg[0]; + + /* Blur source for Sobel: reuse m_gaussianPic (5×5 Gaussian, already computed by + * edgeFilter() during xPreanalyzeQp) when AQ-edge mode ran it; otherwise compute + * a 3×3 box blur inline. */ + pixel* blurSrc; + if (m_param->rc.aqMode == X265_AQ_EDGE) + { + blurSrc = curFrame->m_gaussianPic + + curFrame->m_fencPic->m_lumaMarginY * stride + + curFrame->m_fencPic->m_lumaMarginX; + } + else + { + /* Lazy-allocate once per encode; stride*height is constant for a given source */ + if (!m_noiseBlurBuf) + { + m_noiseBlurBuf = X265_MALLOC(pixel, stride * height); + if (!m_noiseBlurBuf) return -65536; + } + memcpy(m_noiseBlurBuf, src, stride * height * sizeof(pixel)); + gaussianBlur5x5(m_noiseBlurBuf, src, stride, height, width); + blurSrc = m_noiseBlurBuf; + } + + /* Gradient magnitudes via computeEdge — pass NULL edgePic, receive raw float magnitudes + * (cast to int32_t) in gradMag for the adaptive threshold step. */ + int32_t* gradMag = X265_MALLOC(int32_t, stride * height); + if (!gradMag) return -65536; + memset(gradMag, 0, stride * height * sizeof(int32_t)); + + computeEdge(NULL, blurSrc, NULL, stride, height, width, false, EDGE_THRESHOLD, gradMag); + + /* Find peak magnitude then apply adaptive threshold at 15% of peak. + * Sits between Canny's low (10%) and high (30%) thresholds. */ + int32_t maxMag = 1; /* seed at 1 to avoid zero-divide on a fully flat frame */ + for (int i = 1; i < height - 1; ++i) + for (int j = 1; j < width - 1; ++j) + { + int32_t m = gradMag[i * (int)stride + j]; + if (m > maxMag) maxMag = m; + } + int32_t threshold = maxMag * 15 / 100; + + int64_t sum = 0, num = 0; + for (int i = 1; i < height - 1; ++i) + for (int j = 1; j < width - 1; ++j) + { + if (gradMag[i * (int)stride + j] >= threshold) continue; + int k = i * (int)stride + j; + /* Weighted Laplacian: centre x4, 4-neighbours x-2, diagonals x+1 */ + int v = 4 * src[k] + - 2 * (src[k-1] + src[k+1] + src[k-(int)stride] + src[k+(int)stride]) + + (src[k-(int)stride-1] + src[k-(int)stride+1] + + src[k+(int)stride-1] + src[k+(int)stride+1]); + sum += abs(v); ++num; + } + X265_FREE(gradMag); + if (num < 16) return -65536; + return (int32_t)((sum * 82137) / (6 * num * (1 << (X265_DEPTH - 8)))); +} + Lookahead::Lookahead(x265_param *param, ThreadPool* pool) { m_param = param; @@ -977,8 +1044,10 @@ Lookahead::Lookahead(x265_param *param, ThreadPool* pool) m_lastNonB = NULL; m_isSceneTransition = false; - m_scratch = NULL; - m_tld = NULL; + m_scratch = NULL; + m_tld = NULL; + m_noiseBlurBuf = NULL; + m_filterThisGOP = false; m_filled = false; m_outputSignalRequired = false; m_isActive = true; @@ -1198,6 +1267,7 @@ void Lookahead::destroy() X265_FREE(m_accHistDiffRunningAvgCr); X265_FREE(m_accHistDiffRunningAvg[0]); X265_FREE(m_accHistDiffRunningAvg); + X265_FREE(m_noiseBlurBuf); X265_FREE(m_scratch); delete [] m_tld; if (m_param->lookaheadThreads > 0) @@ -2168,7 +2238,29 @@ void Lookahead::slicetypeDecide() Frame* frameEnc = m_inputQueue.first(); for (int b = 0; b < m_inputQueue.size(); b++) { - if (isFilterThisframe(frameEnc->m_mcstf->m_sliceTypeConfig, frameEnc->m_lowres.sliceType)) + /* Noise gate: re-evaluate at every GOP boundary (IDR/I/scenecut). + * m_filterThisGOP persists across batches so B/P frames that arrive + * before the next I-frame inherit the previous GOP's decision. + * When selective-mcstf is off, always filter (preserve prior behavior). */ + if (m_param->bSelectiveMCSTF) + { + if (frameEnc->m_lowres.sliceType == X265_TYPE_IDR || + frameEnc->m_lowres.sliceType == X265_TYPE_I || + frameEnc->m_lowres.bScenecut) + { + int32_t score = estimateNoise(frameEnc); + frameEnc->m_lowres.noiseScore = score; + m_filterThisGOP = (score >= NOISE_THRESHOLD); + } + } + else + { + m_filterThisGOP = true; + } + /* Stamp the per-frame flag so frameencoder reads a race-free value */ + frameEnc->m_lowres.filterThisGOP = m_filterThisGOP; + + if (frameEnc->m_lowres.filterThisGOP && isFilterThisframe(frameEnc->m_mcstf->m_sliceTypeConfig, frameEnc->m_lowres.sliceType)) { if (!generatemcstf(frameEnc, m_origPicBuf->m_mcstfPicList, m_inputQueue.last()->m_poc)) { diff --git a/source/encoder/slicetype.h b/source/encoder/slicetype.h index 373b95dd2..df75a838f 100644 --- a/source/encoder/slicetype.h +++ b/source/encoder/slicetype.h @@ -163,6 +163,8 @@ class Lookahead : public JobProvider x265_param* m_param; Lowres* m_lastNonB; int* m_scratch; // temp buffer for cutree propagate + pixel* m_noiseBlurBuf; // persistent blur buffer for estimateNoise() fallback path + bool m_filterThisGOP; // noise gate decision for the GOP currently being dispatched /* pre-lookahead */ int m_fullQueueSize; @@ -236,6 +238,7 @@ class Lookahead : public JobProvider int findSliceType(int poc); bool generatemcstf(Frame * frame, PicList refPic, int poclast); bool isFilterThisframe(uint8_t sliceTypeConfig, int curSliceType); + int32_t estimateNoise(Frame* curFrame); protected: @@ -340,6 +343,6 @@ class CostEstimateGroup : public BondedTaskGroup CostEstimateGroup& operator=(const CostEstimateGroup&); }; -bool computeEdge(pixel* edgePic, pixel* refPic, pixel* edgeTheta, intptr_t stride, int height, int width, bool bcalcTheta, pixel whitePixel = EDGE_THRESHOLD); +bool computeEdge(pixel* edgePic, pixel* refPic, pixel* edgeTheta, intptr_t stride, int height, int width, bool bcalcTheta, pixel whitePixel = EDGE_THRESHOLD, int32_t* gradMag = NULL); } #endif // ifndef X265_SLICETYPE_H diff --git a/source/x265.h b/source/x265.h index d07826cf4..f50cfeb9d 100644 --- a/source/x265.h +++ b/source/x265.h @@ -320,6 +320,8 @@ typedef struct x265_frame_stats int64_t currTrBitrate; double currTrCRF; int currTrQP; + int32_t frameNoise; /* noise score at GOP start (selective-mcstf); -1 for non-GOP-start frames */ + int isMCSTFEnabled; /* 1 if MCSTF bilateral filter will be applied to this frame */ } x265_frame_stats; typedef struct x265_ctu_info_t @@ -650,6 +652,7 @@ typedef enum #define X265_MAX_GOP_CONFIG 3 #define X265_MAX_GOP_LENGTH 16 #define MAX_T_LAYERS 7 +#define NOISE_THRESHOLD 40000 #if ENABLE_MULTIVIEW #define MAX_VIEWS 2 @@ -2338,6 +2341,9 @@ typedef struct x265_param /*Motion compensated temporal filter*/ int bEnableTemporalFilter; + int mcstfFrameRange; + /* When enabled, estimate noise at each GOP boundary and skip MCSTF for clean GOPs */ + int bSelectiveMCSTF; /* Threaded ME */ /* Number of CTUs processed at once when a worker thread picks up a task from ThreadedME. */ @@ -2348,7 +2354,6 @@ typedef struct x265_param /*SBRC*/ int bEnableSBRC; - int mcstfFrameRange; /*Alpha channel encoding*/ int bEnableAlpha; diff --git a/source/x265cli.cpp b/source/x265cli.cpp index bcb50f5aa..b6a7a91b6 100755 --- a/source/x265cli.cpp +++ b/source/x265cli.cpp @@ -391,6 +391,7 @@ namespace X265_NS { H0(" --dup-threshold PSNR threshold for Frame duplication. Default %d\n", param->dupThreshold); H0(" --[no-]mcstf Enable GOP-based temporal filter. Default %d\n", param->bEnableTemporalFilter); H0(" --mcstf-ref-range <0..4> Maximum number of range for MCSTF. Default %d\n", param->mcstfFrameRange); + H0(" --[no-]selective-mcstf Skip MCSTF for GOPs estimated as clean (low noise). Default %d\n", param->bSelectiveMCSTF); #if ENABLE_ALPHA H0(" --alpha Enable alpha channel support. Default %d\n", param->bEnableAlpha); #endif diff --git a/source/x265cli.h b/source/x265cli.h index 81315ebd2..aaf42ba7f 100644 --- a/source/x265cli.h +++ b/source/x265cli.h @@ -359,6 +359,8 @@ static const struct option long_options[] = { "mcstf", no_argument, NULL, 0 }, { "no-mcstf", no_argument, NULL, 0 }, { "mcstf-ref-range", required_argument, NULL, 0 }, + { "selective-mcstf", no_argument, NULL, 0 }, + { "no-selective-mcstf", no_argument, NULL, 0 }, #if ENABLE_ALPHA { "alpha", no_argument, NULL, 0 }, #endif From 72c1d534939494409a3d07eae1475ea54361aeef Mon Sep 17 00:00:00 2001 From: Akilan Sivakumar Date: Mon, 20 Jul 2026 19:03:37 +0530 Subject: [PATCH 21/23] Fix segfault in MCSTF This commit fixes segfault that occurs due to inadequate frames in lookahead(zerolatency / ultrafast case) --- source/encoder/encoder.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/source/encoder/encoder.cpp b/source/encoder/encoder.cpp index 171efd6e6..d6d746903 100644 --- a/source/encoder/encoder.cpp +++ b/source/encoder/encoder.cpp @@ -4270,9 +4270,16 @@ void Encoder::configure(x265_param *p) if (p->bSelectiveMCSTF && !p->bEnableTemporalFilter) p->bEnableTemporalFilter = 1; - if ((p->bEnableTemporalFilter) && (p->bframes < 5)){ - x265_log(p, X265_LOG_WARNING, "Setting the number of B-frames to 5, as MCSTF filter is enabled.\n"); - p->bframes = 5; + if ((p->bEnableTemporalFilter) && (p->mcstfFrameRange > p->lookaheadDepth)) + { + x265_log(p, X265_LOG_WARNING, "MCSTF frame range is greater than lookahead depth. Disabling MCSTF.\n"); + p->bEnableTemporalFilter = 0; + } + + if (p->bSelectiveMCSTF && !p->bEnableTemporalFilter) + { + x265_log(p, X265_LOG_WARNING, "selective-mcstf requires mcstf to be enabled. Disabling selective-mcstf.\n"); + p->bSelectiveMCSTF = 0; } if ((p->bEnableTemporalSubLayers > 2) && !p->bframes) From dbc98cd40de984d29035683ed1939f000632489f Mon Sep 17 00:00:00 2001 From: Kirithika7 Date: Thu, 23 Jul 2026 14:12:28 +0530 Subject: [PATCH 22/23] mcstf:code cleanup This commit reuses the existing variance computation and remove redundant Allocate/Free up gradMag buffer once per encode --- source/common/temporalfilter.cpp | 54 ++------------- source/common/temporalfilter.h | 2 - source/common/vec/temporalfilter-avx2.cpp | 81 ----------------------- source/encoder/slicetype.cpp | 14 ++-- source/encoder/slicetype.h | 1 + source/test/mcstfharness.cpp | 38 ----------- source/test/mcstfharness.h | 1 - 7 files changed, 17 insertions(+), 174 deletions(-) diff --git a/source/common/temporalfilter.cpp b/source/common/temporalfilter.cpp index a99d81981..29c6e8b5e 100644 --- a/source/common/temporalfilter.cpp +++ b/source/common/temporalfilter.cpp @@ -176,49 +176,6 @@ namespace X265_NS { } } - static void lumaBlockAvgVariance_c( - const pixel* origin, - intptr_t stride, - int blockX, - int blockY, - int blockSize, - double* avgOut, - double* varianceOut) - { - const pixel* base = origin + blockY * stride + blockX; - - int64_t totalSum = 0; - - // Pass 1: Compute sum - for (int y = 0; y < blockSize; y++) - { - const pixel* row = base + y * stride; - - for (int x = 0; x < blockSize; x++) - totalSum += row[x]; - } - - const int N = blockSize * blockSize; - const double avg = (double)totalSum / N; - *avgOut = avg; - - // Pass 2: Compute sum of squared differences - double variance = 0.0; - - for (int y = 0; y < blockSize; y++) - { - const pixel* row = base + y * stride; - - for (int x = 0; x < blockSize; x++) - { - double diff = (double)row[x] - avg; - variance += diff * diff; - } - } - - *varianceOut = variance; - } - static void computeBlockStats_c( const pixel* srcPel, intptr_t srcStride, const pixel* refPel, intptr_t refStride, @@ -293,7 +250,6 @@ namespace X265_NS { { p.motionErrorLumaFrac = motionErrorLumaFrac_c; p.applyMotion = applyMotion_c; - p.lumaBlockAvgVariance = lumaBlockAvgVariance_c; p.computeBlockStats = computeBlockStats_c; p.bilateralFilter = bilateralFilter_c; } @@ -924,10 +880,12 @@ void MotionEstimatorTLD::motionEstimationLumaDoubleRes(MV *mvs, uint32_t mvStrid } } - double avg, variance; - mcstfPrim.lumaBlockAvgVariance( - orig->m_picOrg[0], orig->m_stride, - blockX, blockY, blockSize, &avg, &variance); + const pixel* blkOrigin = orig->m_picOrg[0] + blockY * orig->m_stride + blockX; + uint64_t sumSqr = primitives.cu[partitionFromSizes(blockSize, blockSize)].var(blkOrigin, orig->m_stride); + uint32_t sum = (uint32_t)sumSqr; + uint32_t sqr = (uint32_t)(sumSqr >> 32); + const int N = blockSize * blockSize; + double variance = (double)sqr - ((double)sum * sum) / N; leastError = (int)(20 * ((leastError + 5.0) / (variance + 5.0)) + (leastError / (blockSize * blockSize)) / 50); diff --git a/source/common/temporalfilter.h b/source/common/temporalfilter.h index f26d7041c..0fe71b0ce 100644 --- a/source/common/temporalfilter.h +++ b/source/common/temporalfilter.h @@ -78,8 +78,6 @@ namespace X265_NS { void (*applyMotion)(const pixel* pSrcImage, int srcStride, pixel* pDstImage, int dstStride, int width, int height, int blockSizeX, int blockSizeY, uint32_t mvsStride, const MV* mvs, int csx, int csy, int blockRow, int rowSize, int vShift); - void (*lumaBlockAvgVariance)(const pixel* origin, intptr_t stride, int blockX, int blockY, int blockSize, double* avgOut, double* varianceOut); - void (*computeBlockStats)(const pixel* srcPel, intptr_t srcStride, const pixel* refPel, intptr_t refStride, int blkSize, int* outVariance, int* outDiffsum); void (*bilateralFilter)(const pixel* srcBlk, intptr_t srcStride, int numRefs, const pixel* const* refBlks, const intptr_t* refStrides, diff --git a/source/common/vec/temporalfilter-avx2.cpp b/source/common/vec/temporalfilter-avx2.cpp index cbe7a407b..bdb40045b 100644 --- a/source/common/vec/temporalfilter-avx2.cpp +++ b/source/common/vec/temporalfilter-avx2.cpp @@ -491,86 +491,6 @@ namespace X265_NS { } } - MCSTF_TARGET_AVX2 - static void lumaBlockAvgVariance_avx2(const pixel* origin, intptr_t stride, int blockX, int blockY, int blockSize, double* avgOut, double* varianceOut) - { - X265_CHECK((blockSize & 7) == 0, "Supports blockSize multiple of 8"); - - const pixel* base = origin + blockY * stride + blockX; - - __m256i sumAcc = _mm256_setzero_si256(); - - for (int y1 = 0; y1 < blockSize; y1++) - { - const pixel* row = base + y1 * stride; - - for (int x1 = 0; x1 < blockSize; x1 += 8) - { -#if X265_DEPTH > 8 - __m256i px = _mm256_cvtepu16_epi32( - _mm_loadu_si128((const __m128i*)(row + x1))); -#else - __m256i px = _mm256_cvtepu8_epi32( - _mm_loadl_epi64((const __m128i*)(row + x1))); -#endif - sumAcc = _mm256_add_epi32(sumAcc, px); - } - } - - __m128i lo128 = _mm256_castsi256_si128(sumAcc); - __m128i hi128 = _mm256_extracti128_si256(sumAcc, 1); - __m128i sum128 = _mm_add_epi32(lo128, hi128); - __m128i s64lo = _mm_cvtepi32_epi64(sum128); - __m128i s64hi = _mm_cvtepi32_epi64(_mm_srli_si128(sum128, 8)); - __m128i s64 = _mm_add_epi64(s64lo, s64hi); - int64_t sum_s[2]; - _mm_storeu_si128((__m128i*)sum_s, s64); - int64_t totalSum = sum_s[0] + sum_s[1]; - - const int N = blockSize * blockSize; - const double avg = (double)totalSum / N; - *avgOut = avg; - - const __m256d vavg = _mm256_set1_pd(avg); - __m256d varAcc = _mm256_setzero_pd(); - - for (int y1 = 0; y1 < blockSize; y1++) - { - const pixel* row = base + y1 * stride; - - for (int x1 = 0; x1 < blockSize; x1 += 8) - { -#if X265_DEPTH > 8 - __m128i px8 = _mm_loadu_si128((const __m128i*)(row + x1)); - /* Widen uint16 → int32, then split into two groups of 4 */ - __m256i px32 = _mm256_cvtepu16_epi32(px8); - __m128i lo4 = _mm256_castsi256_si128(px32); - __m128i hi4 = _mm256_extracti128_si256(px32, 1); -#else - __m128i px8 = _mm_loadl_epi64((const __m128i*)(row + x1)); - __m256i px32 = _mm256_cvtepu8_epi32(px8); - __m128i lo4 = _mm256_castsi256_si128(px32); - __m128i hi4 = _mm256_extracti128_si256(px32, 1); -#endif - __m256d dlo = _mm256_cvtepi32_pd(lo4); - __m256d dhi = _mm256_cvtepi32_pd(hi4); - - __m256d dlo_d = _mm256_sub_pd(dlo, vavg); - __m256d dhi_d = _mm256_sub_pd(dhi, vavg); - - varAcc = _mm256_add_pd(varAcc, _mm256_mul_pd(dlo_d, dlo_d)); - varAcc = _mm256_add_pd(varAcc, _mm256_mul_pd(dhi_d, dhi_d)); - } - } - - __m128d vlo2 = _mm256_castpd256_pd128(varAcc); - __m128d vhi2 = _mm256_extractf128_pd(varAcc, 1); - __m128d vsum = _mm_add_pd(vlo2, vhi2); - double varLanes[2]; - _mm_storeu_pd(varLanes, vsum); - *varianceOut = varLanes[0] + varLanes[1]; - } - MCSTF_TARGET_AVX2 static void computeBlockStats_avx2(const pixel* srcPel, intptr_t srcStride, const pixel* refPel, intptr_t refStride, int blkSize, int* outVariance, int* outDiffsum) { @@ -733,7 +653,6 @@ namespace X265_NS { { p.motionErrorLumaFrac = motionErrorLumaFrac_avx2; p.applyMotion = applyMotion_avx2; - p.lumaBlockAvgVariance = lumaBlockAvgVariance_avx2; p.computeBlockStats = computeBlockStats_avx2; p.bilateralFilter = bilateralFilter_avx2; } diff --git a/source/encoder/slicetype.cpp b/source/encoder/slicetype.cpp index be8dfec37..1bfeda9f9 100644 --- a/source/encoder/slicetype.cpp +++ b/source/encoder/slicetype.cpp @@ -1001,9 +1001,14 @@ int32_t Lookahead::estimateNoise(Frame* curFrame) } /* Gradient magnitudes via computeEdge — pass NULL edgePic, receive raw float magnitudes - * (cast to int32_t) in gradMag for the adaptive threshold step. */ - int32_t* gradMag = X265_MALLOC(int32_t, stride * height); - if (!gradMag) return -65536; + * (cast to int32_t) in gradMag for the adaptive threshold step. + * Lazy-allocate once per encode; stride*height is constant for a given source. */ + if (!m_gradMagBuf) + { + m_gradMagBuf = X265_MALLOC(int32_t, stride * height); + if (!m_gradMagBuf) return -65536; + } + int32_t* gradMag = m_gradMagBuf; memset(gradMag, 0, stride * height * sizeof(int32_t)); computeEdge(NULL, blurSrc, NULL, stride, height, width, false, EDGE_THRESHOLD, gradMag); @@ -1032,7 +1037,6 @@ int32_t Lookahead::estimateNoise(Frame* curFrame) + src[k+(int)stride-1] + src[k+(int)stride+1]); sum += abs(v); ++num; } - X265_FREE(gradMag); if (num < 16) return -65536; return (int32_t)((sum * 82137) / (6 * num * (1 << (X265_DEPTH - 8)))); } @@ -1047,6 +1051,7 @@ Lookahead::Lookahead(x265_param *param, ThreadPool* pool) m_scratch = NULL; m_tld = NULL; m_noiseBlurBuf = NULL; + m_gradMagBuf = NULL; m_filterThisGOP = false; m_filled = false; m_outputSignalRequired = false; @@ -1268,6 +1273,7 @@ void Lookahead::destroy() X265_FREE(m_accHistDiffRunningAvg[0]); X265_FREE(m_accHistDiffRunningAvg); X265_FREE(m_noiseBlurBuf); + X265_FREE(m_gradMagBuf); X265_FREE(m_scratch); delete [] m_tld; if (m_param->lookaheadThreads > 0) diff --git a/source/encoder/slicetype.h b/source/encoder/slicetype.h index df75a838f..7b4ba0f8e 100644 --- a/source/encoder/slicetype.h +++ b/source/encoder/slicetype.h @@ -164,6 +164,7 @@ class Lookahead : public JobProvider Lowres* m_lastNonB; int* m_scratch; // temp buffer for cutree propagate pixel* m_noiseBlurBuf; // persistent blur buffer for estimateNoise() fallback path + int32_t* m_gradMagBuf; // persistent gradient-magnitude buffer for estimateNoise() bool m_filterThisGOP; // noise gate decision for the GOP currently being dispatched /* pre-lookahead */ diff --git a/source/test/mcstfharness.cpp b/source/test/mcstfharness.cpp index 251a9e026..20ead9674 100644 --- a/source/test/mcstfharness.cpp +++ b/source/test/mcstfharness.cpp @@ -128,34 +128,6 @@ bool MCSTFHarness::check_applyMotion(const MCSTFPrimitives& ref, const MCSTFPrim return true; } -bool MCSTFHarness::check_lumaBlockAvgVariance(const MCSTFPrimitives& ref, const MCSTFPrimitives& opt) -{ - const int blockSizes[] = { 8, 16 }; - - for (size_t bi = 0; bi < sizeof(blockSizes) / sizeof(int); bi++) - { - int blockSize = blockSizes[bi]; - - for (int i = 0; i < ITERS; i++) - { - double avgRef, varRef, avgOpt, varOpt; - ref.lumaBlockAvgVariance(m_origBuf, TEST_STRIDE, CENTER, CENTER, blockSize, &avgRef, &varRef); - opt.lumaBlockAvgVariance(m_origBuf, TEST_STRIDE, CENTER, CENTER, blockSize, &avgOpt, &varOpt); - - double avgTol = 1e-6 * X265_MAX(1.0, fabs(avgRef)); - double varTol = 1e-6 * X265_MAX(1.0, fabs(varRef)); - - if (fabs(avgRef - avgOpt) > avgTol || fabs(varRef - varOpt) > varTol) - { - printf("lumaBlockAvgVariance[bs=%d]: failed (avg %.6f vs %.6f, var %.6f vs %.6f)\n", - blockSize, avgRef, avgOpt, varRef, varOpt); - return false; - } - } - } - return true; -} - bool MCSTFHarness::check_computeBlockStats(const MCSTFPrimitives& ref, const MCSTFPrimitives& opt) { const int blkSizes[] = { 4, 8 }; @@ -238,8 +210,6 @@ bool MCSTFHarness::testCorrectness(const MCSTFPrimitives& ref, const MCSTFPrimit return false; if (opt.applyMotion && !check_applyMotion(ref, opt)) return false; - if (opt.lumaBlockAvgVariance && !check_lumaBlockAvgVariance(ref, opt)) - return false; if (opt.computeBlockStats && !check_computeBlockStats(ref, opt)) return false; if (opt.bilateralFilter && !check_bilateralFilter(ref, opt)) @@ -265,14 +235,6 @@ void MCSTFHarness::measureSpeed(const MCSTFPrimitives& ref, const MCSTFPrimitive planeOrigin, TEST_STRIDE, m_dstBufOpt, TEST_STRIDE, 64, 64, 8, 8, 8, mvs, 0, 0, 0, 0, 0); } - if (opt.lumaBlockAvgVariance) - { - double avg, variance; - printf("lumaBlockAvgVariance[bs=8]\t"); - REPORT_SPEEDUP(opt.lumaBlockAvgVariance, ref.lumaBlockAvgVariance, - m_origBuf, TEST_STRIDE, CENTER, CENTER, 8, &avg, &variance); - } - if (opt.computeBlockStats) { int variance, diffsum; diff --git a/source/test/mcstfharness.h b/source/test/mcstfharness.h index 0ba4edaf2..a0ee6eae0 100644 --- a/source/test/mcstfharness.h +++ b/source/test/mcstfharness.h @@ -45,7 +45,6 @@ class MCSTFHarness bool check_motionErrorLumaFrac(const MCSTFPrimitives& ref, const MCSTFPrimitives& opt); bool check_applyMotion(const MCSTFPrimitives& ref, const MCSTFPrimitives& opt); - bool check_lumaBlockAvgVariance(const MCSTFPrimitives& ref, const MCSTFPrimitives& opt); bool check_computeBlockStats(const MCSTFPrimitives& ref, const MCSTFPrimitives& opt); bool check_bilateralFilter(const MCSTFPrimitives& ref, const MCSTFPrimitives& opt); From e88d4748aaa4ecc401177c7786d4d2c2e481da03 Mon Sep 17 00:00:00 2001 From: Kirithika7 Date: Fri, 24 Jul 2026 13:06:50 +0530 Subject: [PATCH 23/23] mcstf:fix code quality issues --- source/common/temporalfilter.cpp | 6 +- source/common/temporalfilter.h | 9 +-- source/common/vec/temporalfilter-avx2.cpp | 88 +++++++++++------------ source/encoder/slicetype.cpp | 10 +-- source/encoder/slicetype.h | 2 +- source/test/mcstfharness.cpp | 4 ++ source/test/testharness.h | 4 +- 7 files changed, 62 insertions(+), 61 deletions(-) diff --git a/source/common/temporalfilter.cpp b/source/common/temporalfilter.cpp index 29c6e8b5e..7c3bdc683 100644 --- a/source/common/temporalfilter.cpp +++ b/source/common/temporalfilter.cpp @@ -640,7 +640,7 @@ void TemporalFilter::bilateralFilter(Frame* curFrame, TemporalFilterRefPicInfo* } void MotionEstimatorTLD::motionEstimationLuma(MV *mvs, uint32_t mvStride, pixel* src,int stride, int height, int width, pixel* buf, - int row, const int rowSize, MV* previous, uint32_t prevMvStride, int factor) + int row, const int rowSize, const MV* previous, uint32_t prevMvStride, int factor) { int range = m_searchRange; @@ -655,7 +655,7 @@ void MotionEstimatorTLD::motionEstimationLuma(MV *mvs, uint32_t mvStride, pixel* if (rowStart > height) return; - int rowEnd = (!rowSize) ? height : X265_MIN(rowStart + rowSize, height); + int rowEnd = (!rowSize) ? height : X265_MIN(rowStart + rowSize, height); int error; for (int blockY = rowStart; blockY + blockSize <= rowEnd; blockY += stepSize) @@ -749,7 +749,7 @@ void MotionEstimatorTLD::motionEstimationLuma(MV *mvs, uint32_t mvStride, pixel* void MotionEstimatorTLD::motionEstimationLumaDoubleRes(MV *mvs, uint32_t mvStride, PicYuv *orig, PicYuv *buffer, - MV *previous, uint32_t prevMvStride, int factor, int* minError, int row, const int rowSize) + const MV *previous, uint32_t prevMvStride, int factor, int* minError, int row, const int rowSize) { int range = 0; diff --git a/source/common/temporalfilter.h b/source/common/temporalfilter.h index 0fe71b0ce..7d080b53e 100644 --- a/source/common/temporalfilter.h +++ b/source/common/temporalfilter.h @@ -132,10 +132,10 @@ namespace X265_NS { void init(const x265_param* param); void motionEstimationLuma(MV* mvs, uint32_t mvStride, pixel* src, int stride, int height, int width, pixel* buf, int row, const int rowSize, - MV* previous = 0, uint32_t prevmvStride = 0, int factor = 1); + const MV* previous = 0, uint32_t prevmvStride = 0, int factor = 1); void motionEstimationLumaDoubleRes(MV* mvs, uint32_t mvStride, PicYuv* orig, PicYuv* buffer, - MV* previous, uint32_t prevMvStride, int factor, int* minError, int row, const int rowSize); + const MV* previous, uint32_t prevMvStride, int factor, int* minError, int row, const int rowSize); int motionErrorLumaSSD(pixel* src, int stride, @@ -247,12 +247,9 @@ namespace X265_NS { static const int MAX_FILTER_JOBS = 512; FilterJob m_jobs[MAX_FILTER_JOBS]; - int m_jobTotal; - int m_jobAcquired; - Lock m_lock; BilateralFilterGroup(TemporalFilter& f, ThreadPool* pool) - : m_filter(f), m_pool(pool), m_jobTotal(0), m_jobAcquired(0) {} + : m_filter(f), m_pool(pool), m_jobs() {} void add(Frame* frame, TemporalFilterRefPicInfo* mcstfRefList, int numRef, int blockRow, int rowSize) diff --git a/source/common/vec/temporalfilter-avx2.cpp b/source/common/vec/temporalfilter-avx2.cpp index bdb40045b..f5857ab7d 100644 --- a/source/common/vec/temporalfilter-avx2.cpp +++ b/source/common/vec/temporalfilter-avx2.cpp @@ -107,9 +107,9 @@ namespace X265_NS { static inline __m256i load8px_epi32(const pixel* p) { #if X265_DEPTH > 8 - return _mm256_cvtepu16_epi32(_mm_loadu_si128((const __m128i*)p)); + return _mm256_cvtepu16_epi32(_mm_loadu_si128(reinterpret_cast(p))); #else - return _mm256_cvtepu8_epi32(_mm_loadl_epi64((const __m128i*)p)); + return _mm256_cvtepu8_epi32(_mm_loadl_epi64(reinterpret_cast(p))); #endif } @@ -117,9 +117,9 @@ namespace X265_NS { static inline __m256i load4px_epi32(const pixel* p) { #if X265_DEPTH > 8 - return _mm256_cvtepu16_epi32(_mm_loadl_epi64((const __m128i*)p)); + return _mm256_cvtepu16_epi32(_mm_loadl_epi64(reinterpret_cast(p))); #else - return _mm256_cvtepu8_epi32(_mm_cvtsi32_si128(*(const int*)p)); + return _mm256_cvtepu8_epi32(_mm_cvtsi32_si128(*reinterpret_cast(p))); #endif } @@ -132,14 +132,14 @@ namespace X265_NS { __m256i packed = _mm256_packus_epi32(v, v); __m128i out = _mm256_castsi256_si128( _mm256_permute4x64_epi64(packed, 0x08)); - _mm_storeu_si128((__m128i*)dst, out); + _mm_storeu_si128(reinterpret_cast<__m128i*>(dst), out); #else __m256i as16 = _mm256_packs_epi32(v, v); __m128i lo = _mm256_castsi256_si128(as16); __m128i hi = _mm256_extracti128_si256(as16, 1); __m128i merged = _mm_unpacklo_epi64(lo, hi); __m128i out8 = _mm_packus_epi16(merged, merged); - _mm_storel_epi64((__m128i*)dst, out8); + _mm_storel_epi64(reinterpret_cast<__m128i*>(dst), out8); #endif } @@ -149,11 +149,11 @@ namespace X265_NS { __m128i lo = _mm256_castsi256_si128(v); /* only lower 4 lanes valid */ #if X265_DEPTH > 8 __m128i out = _mm_packus_epi32(lo, _mm_setzero_si128()); - _mm_storel_epi64((__m128i*)dst, out); + _mm_storel_epi64(reinterpret_cast<__m128i*>(dst), out); #else __m128i as16 = _mm_packs_epi32(lo, _mm_setzero_si128()); __m128i out8 = _mm_packus_epi16(as16, _mm_setzero_si128()); - *(int*)dst = _mm_cvtsi128_si32(out8); + *reinterpret_cast(dst) = _mm_cvtsi128_si32(out8); #endif } @@ -206,19 +206,19 @@ namespace X265_NS { * [255:128]. We build the 256-bit interleaved pairs: * pairs_ab[255:0] = { unpackhi(s_a, s_b) | unpacklo(s_a, s_b) } * so madd lane i gives s_a[i]*f_a + s_b[i]*f_b. */ - __m128i s1 = _mm_loadu_si128((const __m128i*) & rowStart[1]); - __m128i s2 = _mm_loadu_si128((const __m128i*) & rowStart[2]); - __m128i s3 = _mm_loadu_si128((const __m128i*) & rowStart[3]); - __m128i s4 = _mm_loadu_si128((const __m128i*) & rowStart[4]); - __m128i s5 = _mm_loadu_si128((const __m128i*) & rowStart[5]); - __m128i s6 = _mm_loadu_si128((const __m128i*) & rowStart[6]); + __m128i s1 = _mm_loadu_si128(reinterpret_cast(&rowStart[1])); + __m128i s2 = _mm_loadu_si128(reinterpret_cast(&rowStart[2])); + __m128i s3 = _mm_loadu_si128(reinterpret_cast(&rowStart[3])); + __m128i s4 = _mm_loadu_si128(reinterpret_cast(&rowStart[4])); + __m128i s5 = _mm_loadu_si128(reinterpret_cast(&rowStart[5])); + __m128i s6 = _mm_loadu_si128(reinterpret_cast(&rowStart[6])); #else - __m128i s1 = _mm_cvtepu8_epi16(_mm_loadl_epi64((const __m128i*) & rowStart[1])); - __m128i s2 = _mm_cvtepu8_epi16(_mm_loadl_epi64((const __m128i*) & rowStart[2])); - __m128i s3 = _mm_cvtepu8_epi16(_mm_loadl_epi64((const __m128i*) & rowStart[3])); - __m128i s4 = _mm_cvtepu8_epi16(_mm_loadl_epi64((const __m128i*) & rowStart[4])); - __m128i s5 = _mm_cvtepu8_epi16(_mm_loadl_epi64((const __m128i*) & rowStart[5])); - __m128i s6 = _mm_cvtepu8_epi16(_mm_loadl_epi64((const __m128i*) & rowStart[6])); + __m128i s1 = _mm_cvtepu8_epi16(_mm_loadl_epi64(reinterpret_cast(&rowStart[1]))); + __m128i s2 = _mm_cvtepu8_epi16(_mm_loadl_epi64(reinterpret_cast(&rowStart[2]))); + __m128i s3 = _mm_cvtepu8_epi16(_mm_loadl_epi64(reinterpret_cast(&rowStart[3]))); + __m128i s4 = _mm_cvtepu8_epi16(_mm_loadl_epi64(reinterpret_cast(&rowStart[4]))); + __m128i s5 = _mm_cvtepu8_epi16(_mm_loadl_epi64(reinterpret_cast(&rowStart[5]))); + __m128i s6 = _mm_cvtepu8_epi16(_mm_loadl_epi64(reinterpret_cast(&rowStart[6]))); #endif __m256i pairs12 = _mm256_set_m128i( @@ -237,7 +237,7 @@ namespace X265_NS { _mm256_madd_epi16(pairs34, xf34)), _mm256_madd_epi16(pairs56, xf56)); - _mm256_storeu_si256((__m256i*) & tempArray[y1][x1], h_out); + _mm256_storeu_si256(reinterpret_cast<__m256i*>(&tempArray[y1][x1]), h_out); } } @@ -260,12 +260,12 @@ namespace X265_NS { for (int x1 = 0; x1 < bs; x1 += 8) { - __m256i t1 = _mm256_loadu_si256((const __m256i*) & tempArray[outY + 1][x1]); - __m256i t2 = _mm256_loadu_si256((const __m256i*) & tempArray[outY + 2][x1]); - __m256i t3 = _mm256_loadu_si256((const __m256i*) & tempArray[outY + 3][x1]); - __m256i t4 = _mm256_loadu_si256((const __m256i*) & tempArray[outY + 4][x1]); - __m256i t5 = _mm256_loadu_si256((const __m256i*) & tempArray[outY + 5][x1]); - __m256i t6 = _mm256_loadu_si256((const __m256i*) & tempArray[outY + 6][x1]); + __m256i t1 = _mm256_loadu_si256(reinterpret_cast(&tempArray[outY + 1][x1])); + __m256i t2 = _mm256_loadu_si256(reinterpret_cast(&tempArray[outY + 2][x1])); + __m256i t3 = _mm256_loadu_si256(reinterpret_cast(&tempArray[outY + 3][x1])); + __m256i t4 = _mm256_loadu_si256(reinterpret_cast(&tempArray[outY + 4][x1])); + __m256i t5 = _mm256_loadu_si256(reinterpret_cast(&tempArray[outY + 5][x1])); + __m256i t6 = _mm256_loadu_si256(reinterpret_cast(&tempArray[outY + 6][x1])); __m256i v = _mm256_add_epi32( _mm256_add_epi32( @@ -281,9 +281,9 @@ namespace X265_NS { #if X265_DEPTH > 8 __m256i orig = _mm256_cvtepu16_epi32( - _mm_loadu_si128((const __m128i*) & origRowBase[x1])); + _mm_loadu_si128(reinterpret_cast(&origRowBase[x1]))); #else - __m128i xorig = _mm_loadl_epi64((const __m128i*) & origRowBase[x1]); + __m128i xorig = _mm_loadl_epi64(reinterpret_cast(&origRowBase[x1])); __m256i orig = _mm256_set_m128i( _mm_cvtepu8_epi32(_mm_srli_si128(xorig, 4)), _mm_cvtepu8_epi32(xorig)); @@ -363,7 +363,7 @@ namespace X265_NS { acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(s5, xf5)); acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(s6, xf6)); - _mm256_storeu_si256((__m256i*) & tempArray[by][0], acc); + _mm256_storeu_si256(reinterpret_cast<__m256i*>(&tempArray[by][0]), acc); } } else @@ -386,7 +386,7 @@ namespace X265_NS { acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(s5, xf5)); acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(s6, xf6)); - _mm_storeu_si128((__m128i*) & tempArray[by][0], + _mm_storeu_si128(reinterpret_cast<__m128i*>(&tempArray[by][0]), _mm256_castsi256_si128(acc)); } } @@ -398,12 +398,12 @@ namespace X265_NS { { pixel* pDstPel = pDstImage + (y + by) * dstStride + x; - __m256i t1 = _mm256_loadu_si256((const __m256i*) & tempArray[by + 1][0]); - __m256i t2 = _mm256_loadu_si256((const __m256i*) & tempArray[by + 2][0]); - __m256i t3 = _mm256_loadu_si256((const __m256i*) & tempArray[by + 3][0]); - __m256i t4 = _mm256_loadu_si256((const __m256i*) & tempArray[by + 4][0]); - __m256i t5 = _mm256_loadu_si256((const __m256i*) & tempArray[by + 5][0]); - __m256i t6 = _mm256_loadu_si256((const __m256i*) & tempArray[by + 6][0]); + __m256i t1 = _mm256_loadu_si256(reinterpret_cast(&tempArray[by + 1][0])); + __m256i t2 = _mm256_loadu_si256(reinterpret_cast(&tempArray[by + 2][0])); + __m256i t3 = _mm256_loadu_si256(reinterpret_cast(&tempArray[by + 3][0])); + __m256i t4 = _mm256_loadu_si256(reinterpret_cast(&tempArray[by + 4][0])); + __m256i t5 = _mm256_loadu_si256(reinterpret_cast(&tempArray[by + 5][0])); + __m256i t6 = _mm256_loadu_si256(reinterpret_cast(&tempArray[by + 6][0])); __m256i acc = _mm256_mullo_epi32(t1, yf1); acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(t2, yf2)); @@ -427,12 +427,12 @@ namespace X265_NS { { pixel* pDstPel = pDstImage + (y + by) * dstStride + x; - __m256i t1 = _mm256_castsi128_si256(_mm_loadu_si128((const __m128i*) & tempArray[by + 1][0])); - __m256i t2 = _mm256_castsi128_si256(_mm_loadu_si128((const __m128i*) & tempArray[by + 2][0])); - __m256i t3 = _mm256_castsi128_si256(_mm_loadu_si128((const __m128i*) & tempArray[by + 3][0])); - __m256i t4 = _mm256_castsi128_si256(_mm_loadu_si128((const __m128i*) & tempArray[by + 4][0])); - __m256i t5 = _mm256_castsi128_si256(_mm_loadu_si128((const __m128i*) & tempArray[by + 5][0])); - __m256i t6 = _mm256_castsi128_si256(_mm_loadu_si128((const __m128i*) & tempArray[by + 6][0])); + __m256i t1 = _mm256_castsi128_si256(_mm_loadu_si128(reinterpret_cast(&tempArray[by + 1][0]))); + __m256i t2 = _mm256_castsi128_si256(_mm_loadu_si128(reinterpret_cast(&tempArray[by + 2][0]))); + __m256i t3 = _mm256_castsi128_si256(_mm_loadu_si128(reinterpret_cast(&tempArray[by + 3][0]))); + __m256i t4 = _mm256_castsi128_si256(_mm_loadu_si128(reinterpret_cast(&tempArray[by + 4][0]))); + __m256i t5 = _mm256_castsi128_si256(_mm_loadu_si128(reinterpret_cast(&tempArray[by + 5][0]))); + __m256i t6 = _mm256_castsi128_si256(_mm_loadu_si128(reinterpret_cast(&tempArray[by + 6][0]))); __m256i acc = _mm256_mullo_epi32(t1, yf1); acc = _mm256_add_epi32(acc, _mm256_mullo_epi32(t2, yf2)); @@ -656,4 +656,4 @@ namespace X265_NS { p.computeBlockStats = computeBlockStats_avx2; p.bilateralFilter = bilateralFilter_avx2; } -} // namespace X265_NS \ No newline at end of file +} // namespace X265_NS diff --git a/source/encoder/slicetype.cpp b/source/encoder/slicetype.cpp index 1bfeda9f9..26f62770b 100644 --- a/source/encoder/slicetype.cpp +++ b/source/encoder/slicetype.cpp @@ -97,7 +97,7 @@ uint32_t acEnergyVarHist(uint64_t sum_ssd, int shift) return ssd - ((uint64_t)sum * sum >> shift); } -bool computeEdge(pixel* edgePic, pixel* refPic, pixel* edgeTheta, intptr_t stride, int height, int width, bool bcalcTheta, pixel whitePixel, int32_t* gradMag) +bool computeEdge(pixel* edgePic, const pixel* refPic, pixel* edgeTheta, intptr_t stride, int height, int width, bool bcalcTheta, pixel whitePixel, int32_t* gradMag) { intptr_t rowOne = 0, rowTwo = 0, rowThree = 0, colOne = 0, colTwo = 0, colThree = 0; intptr_t middle = 0, topLeft = 0, topRight = 0, bottomLeft = 0, bottomRight = 0; @@ -975,12 +975,12 @@ int32_t Lookahead::estimateNoise(Frame* curFrame) int width = curFrame->m_fencPic->m_picWidth; int height = curFrame->m_fencPic->m_picHeight; intptr_t stride = curFrame->m_fencPic->m_stride; - pixel* src = curFrame->m_fencPic->m_picOrg[0]; + const pixel* src = curFrame->m_fencPic->m_picOrg[0]; /* Blur source for Sobel: reuse m_gaussianPic (5×5 Gaussian, already computed by * edgeFilter() during xPreanalyzeQp) when AQ-edge mode ran it; otherwise compute * a 3×3 box blur inline. */ - pixel* blurSrc; + const pixel *blurSrc; if (m_param->rc.aqMode == X265_AQ_EDGE) { blurSrc = curFrame->m_gaussianPic @@ -1011,7 +1011,7 @@ int32_t Lookahead::estimateNoise(Frame* curFrame) int32_t* gradMag = m_gradMagBuf; memset(gradMag, 0, stride * height * sizeof(int32_t)); - computeEdge(NULL, blurSrc, NULL, stride, height, width, false, EDGE_THRESHOLD, gradMag); + computeEdge(NULL, blurSrc, NULL, stride, height, width, false, (pixel)EDGE_THRESHOLD, gradMag); /* Find peak magnitude then apply adaptive threshold at 15% of peak. * Sits between Canny's low (10%) and high (30%) thresholds. */ @@ -2286,7 +2286,7 @@ void Lookahead::slicetypeDecide() for (int j = 1; j <= frameEnc->m_mcstf->m_numRef; j++) { - TemporalFilterRefPicInfo* ref = &frameEnc->m_mcstfRefList[j - 1]; + const TemporalFilterRefPicInfo* ref = &frameEnc->m_mcstfRefList[j - 1]; int refpoc = ref->poc; for (int row = 0; row < numBlockRows; row++) diff --git a/source/encoder/slicetype.h b/source/encoder/slicetype.h index 7b4ba0f8e..36fbeb457 100644 --- a/source/encoder/slicetype.h +++ b/source/encoder/slicetype.h @@ -344,6 +344,6 @@ class CostEstimateGroup : public BondedTaskGroup CostEstimateGroup& operator=(const CostEstimateGroup&); }; -bool computeEdge(pixel* edgePic, pixel* refPic, pixel* edgeTheta, intptr_t stride, int height, int width, bool bcalcTheta, pixel whitePixel = EDGE_THRESHOLD, int32_t* gradMag = NULL); +bool computeEdge(pixel* edgePic, const pixel* refPic, pixel* edgeTheta, intptr_t stride, int height, int width, bool bcalcTheta, pixel whitePixel = (pixel)EDGE_THRESHOLD, int32_t* gradMag = NULL); } #endif // ifndef X265_SLICETYPE_H diff --git a/source/test/mcstfharness.cpp b/source/test/mcstfharness.cpp index 20ead9674..54f176b21 100644 --- a/source/test/mcstfharness.cpp +++ b/source/test/mcstfharness.cpp @@ -222,6 +222,7 @@ void MCSTFHarness::measureSpeed(const MCSTFPrimitives& ref, const MCSTFPrimitive if (opt.motionErrorLumaFrac) { printf("motionErrorLumaFrac[bs=16]\t"); + // cppcheck-suppress unsignedPositive REPORT_SPEEDUP(opt.motionErrorLumaFrac, ref.motionErrorLumaFrac, m_origBuf, TEST_STRIDE, m_refBuf, TEST_STRIDE, CENTER, CENTER, 20, 5, 16, INT_MAX, X265_DEPTH); } @@ -231,6 +232,7 @@ void MCSTFHarness::measureSpeed(const MCSTFPrimitives& ref, const MCSTFPrimitive static MV mvs[64]; pixel* planeOrigin = m_origBuf + PAD * TEST_STRIDE + PAD; printf("applyMotion[8x8]\t"); + // cppcheck-suppress unsignedPositive REPORT_SPEEDUP(opt.applyMotion, ref.applyMotion, planeOrigin, TEST_STRIDE, m_dstBufOpt, TEST_STRIDE, 64, 64, 8, 8, 8, mvs, 0, 0, 0, 0, 0); } @@ -239,6 +241,7 @@ void MCSTFHarness::measureSpeed(const MCSTFPrimitives& ref, const MCSTFPrimitive { int variance, diffsum; printf("computeBlockStats[bs=8]\t"); + // cppcheck-suppress unsignedPositive REPORT_SPEEDUP(opt.computeBlockStats, ref.computeBlockStats, m_origBuf + CENTER * TEST_STRIDE + CENTER, TEST_STRIDE, m_refBuf + CENTER * TEST_STRIDE + CENTER, TEST_STRIDE, 8, &variance, &diffsum); @@ -260,6 +263,7 @@ void MCSTFHarness::measureSpeed(const MCSTFPrimitives& ref, const MCSTFPrimitive const double bdw = 1024.0 / (PIXEL_MAX + 1); printf("bilateralFilter[bs=8,refs=4]\t"); + // cppcheck-suppress unsignedPositive REPORT_SPEEDUP(opt.bilateralFilter, ref.bilateralFilter, srcBlk, TEST_STRIDE, 4, refBlks, refStrides, vww, vsw, bdw, (double)PIXEL_MAX, 8, m_dstBufOpt, TEST_STRIDE); } diff --git a/source/test/testharness.h b/source/test/testharness.h index 64aa70733..64828710d 100644 --- a/source/test/testharness.h +++ b/source/test/testharness.h @@ -107,7 +107,7 @@ static inline uint32_t __rdtsc(void) * Then measures the C reference with BENCH_RUNS / 4 runs and reports X factor and average cycles.*/ #define REPORT_SPEEDUP(RUNOPT, RUNREF, ...) \ { \ - uint32_t cycles = 0; int runs = 0; \ + uint32_t cycles = 0; uint32_t runs = 0; \ RUNOPT(__VA_ARGS__); \ for (int ti = 0; ti < BENCH_RUNS; ti++) { \ uint32_t t0 = (uint32_t)__rdtsc(); \ @@ -118,7 +118,7 @@ static inline uint32_t __rdtsc(void) uint32_t t1 = (uint32_t)__rdtsc() - t0; \ if (t1 * runs <= cycles * 4 && ti > 0) { cycles += t1; runs++; } \ } \ - uint32_t refcycles = 0; int refruns = 0; \ + uint32_t refcycles = 0; uint32_t refruns = 0; \ RUNREF(__VA_ARGS__); \ for (int ti = 0; ti < BENCH_RUNS / 4; ti++) { \ uint32_t t0 = (uint32_t)__rdtsc(); \