565 lines · 20.5 KB · C++
RawDownload
#include <stdlib.h>#include "gtest/gtest.h"#include "utils/DataGenerator.h"#include "md.h"#include "sample.h"#include "svc_motion_estimate.h"#include "wels_func_ptr_def.h"#include "cpu.h" using namespace WelsEnc; void CopyTargetBlock (uint8_t* pSrcBlock, const int32_t kiBlockSize, SMVUnitXY sTargetMv, const int32_t kiRefPicStride,                      uint8_t* pRefPic) {  uint8_t* pTargetPos = pRefPic + sTargetMv.iMvY * kiRefPicStride + sTargetMv.iMvX;  uint8_t* pSourcePos = pSrcBlock;   for (int i = 0; i < kiBlockSize; i++) {    memcpy (pSourcePos, pTargetPos, kiBlockSize * sizeof (uint8_t));    pTargetPos += kiRefPicStride;    pSourcePos += kiBlockSize;  }}  void InitMe (const uint8_t kuiQp, const uint32_t kuiMvdTableMiddle, const uint32_t kuiMvdTableStride,             uint16_t* pMvdCostTable, SWelsME* pMe) {  MvdCostInit (pMvdCostTable, kuiMvdTableStride);  pMe->pMvdCost = &pMvdCostTable[kuiQp * kuiMvdTableStride + kuiMvdTableMiddle];  pMe->sMvp.iMvX = pMe->sMvp.iMvY = 0;  pMe->sMvBase.iMvX = pMe->sMvBase.iMvY = 0;  pMe->sMv.iMvX = pMe->sMv.iMvY = 0;} class MotionEstimateTest : public ::testing::Test { public:  virtual void SetUp() {    m_pRefData = NULL;    m_pSrcBlock = NULL;    m_pMvdCostTable = NULL;     m_iWidth = 64;//size of search window    m_iHeight = 64;//size of search window    m_iMaxSearchBlock = 16;    m_uiMvdTableSize	= (1 + (648 << 1));     pMa = new CMemoryAlign (0);    m_pRefData = static_cast<uint8_t*>                 (pMa->WelsMalloc (m_iWidth * m_iHeight, "RefPic"));    ASSERT_TRUE (NULL != m_pRefData);    m_pSrcBlock = static_cast<uint8_t*>                  (pMa->WelsMalloc (m_iMaxSearchBlock * m_iMaxSearchBlock, "SrcBlock"));    ASSERT_TRUE (NULL != m_pSrcBlock);    m_pMvdCostTable = new uint16_t[52 * m_uiMvdTableSize];    ASSERT_TRUE (NULL != m_pMvdCostTable);  }  void DoLineTest (PLineFullSearchFunc func, bool horizontal);  virtual void TearDown() {    delete [] m_pMvdCostTable;    pMa->WelsFree (m_pRefData, "RefPic");    pMa->WelsFree (m_pSrcBlock, "SrcBlock");    delete pMa;  } public:  uint8_t* m_pRefData;  uint8_t* m_pSrcBlock;  uint32_t m_uiMvdTableSize;  uint16_t* m_pMvdCostTable;   int32_t m_iWidth;  int32_t m_iHeight;  int32_t m_iMaxSearchBlock;  CMemoryAlign* pMa;};  TEST_F (MotionEstimateTest, TestDiamondSearch) {#define TEST_POS (5)  const int32_t kiPositionToCheck[TEST_POS][2] = {{0, 0}, {0, 1}, {1, 0}, {0, -1}, { -1, 0}};  const int32_t kiMaxBlock16Sad = 72000;//a rough number  SWelsFuncPtrList sFuncList;  SWelsME sMe;  SSlice sSlice;  memset (&sSlice, 0, sizeof (sSlice));   const uint8_t kuiQp = rand() % 52;  InitMe (kuiQp, 648, m_uiMvdTableSize, m_pMvdCostTable, &sMe);   SMVUnitXY sTargetMv;  WelsInitSampleSadFunc (&sFuncList, 0); //test c functions   uint8_t* pRefPicCenter = m_pRefData + (m_iHeight / 2) * m_iWidth + (m_iWidth / 2);  bool bDataGeneratorSucceed = false;  bool bFoundMatch = false;  int32_t i, iTryTimes;  for (i = 0; i < TEST_POS; i++) {    sTargetMv.iMvX = kiPositionToCheck[i][0];    sTargetMv.iMvY = kiPositionToCheck[i][1];    iTryTimes = 100;    bDataGeneratorSucceed = false;    bFoundMatch = false;    while (!bFoundMatch && (iTryTimes--) > 0) {      if (!YUVPixelDataGenerator (m_pRefData, m_iWidth, m_iHeight, m_iWidth))        continue;       bDataGeneratorSucceed = true;      CopyTargetBlock (m_pSrcBlock, 16, sTargetMv, m_iWidth, pRefPicCenter);       //clean the sMe status      sMe.uiBlockSize = rand() % 5;      sMe.pEncMb = m_pSrcBlock;      sMe.pRefMb = pRefPicCenter;      sMe.sMv.iMvX = sMe.sMv.iMvY = 0;      sMe.uiSadCost = sMe.uiSatdCost = kiMaxBlock16Sad;      WelsDiamondSearch (&sFuncList, &sMe, &sSlice, m_iMaxSearchBlock, m_iWidth);       //the last selection may be affected by MVDcost, that is when (0,0) will be better      //when comparing (1,1) and (1,0), due to the difference between MVD cost, it is possible that (1,0) is selected while the best match is (1,1)      bFoundMatch = ((sMe.sMv.iMvX == (sTargetMv.iMvX)) || (sMe.sMv.iMvX == 0)) && ((sMe.sMv.iMvY == (sTargetMv.iMvY))                    || (sMe.sMv.iMvY == 0));    }    if (bDataGeneratorSucceed) {      //if DataGenerator never succeed, there is no meaning to check iTryTimes      ASSERT_TRUE (iTryTimes > 0);      //it is possible that ref at differnt position is identical, but that should be under a low probability    }  }} class MotionEstimateRangeTest : public ::testing::Test { public:  virtual void SetUp() {     m_iWidth = 320;    m_iHeight = 240;    m_iWidthExt = m_iWidth + 2 * PADDING_LENGTH;    m_iHeightExt = m_iHeight + 2 * PADDING_LENGTH;    m_iMbWidth = m_iWidth >> 4;    m_iMbHeight = m_iHeight >> 4;     m_iUsageType = 0;    m_iNumDependencyLayers = 1;    m_iMvRange = m_iUsageType ? EXPANDED_MV_RANGE : CAMERA_STARTMV_RANGE;    m_iMvdRange = (m_iUsageType ? EXPANDED_MVD_RANGE : ((m_iNumDependencyLayers == 1) ? CAMERA_MVD_RANGE :                   CAMERA_HIGHLAYER_MVD_RANGE));    m_uiMvdInterTableSize	= (m_iMvdRange << 2); //intepel*4=qpel    m_uiMvdInterTableStride	=  1 + (m_uiMvdInterTableSize << 1);//qpel_mv_range*2=(+/-);    m_uiMvdCacheAlignedSize	= m_uiMvdInterTableStride * sizeof (uint16_t);     m_pMa = new CMemoryAlign (16);    ASSERT_TRUE (NULL != m_pMa);    m_pMvdCostTable = (uint16_t*)m_pMa->WelsMallocz (52 * m_uiMvdCacheAlignedSize, "pMvdCostTable");    ASSERT_TRUE (NULL != m_pMvdCostTable);    m_pRefStart = (uint8_t*)m_pMa->WelsMallocz (m_iWidthExt * m_iHeightExt, "reference frame ");    ASSERT_TRUE (NULL != m_pRefStart);    m_pSrc = (uint8_t*)m_pMa->WelsMallocz (m_iWidth * m_iHeight, "source frame");    ASSERT_TRUE (NULL != m_pSrc);   }  virtual void TearDown() {    if (!m_pMa)      return;    if (m_pRefStart) {      m_pMa->WelsFree (m_pRefStart, "reference frame ");      m_pRefStart = NULL;    }    if (m_pSrc) {      m_pMa->WelsFree (m_pSrc, "source frame ");      m_pSrc = NULL;    }    if (m_pMvdCostTable) {      m_pMa->WelsFree (m_pMvdCostTable, "pMvdCostTable");      m_pMvdCostTable = NULL;    }    if (m_pMa) {      delete m_pMa;      m_pMa = NULL;    }  } public:  uint8_t* m_pRefStart;  uint8_t* m_pSrc;  uint16_t* m_pMvdCostTable;   int32_t m_iWidth;  int32_t m_iHeight;  int32_t m_iWidthExt;  int32_t m_iHeightExt;  int32_t m_iMbWidth;  int32_t m_iMbHeight;  CMemoryAlign* m_pMa;   int32_t m_iMvRange;  int32_t m_iMvdRange;  uint32_t m_uiMvdInterTableSize;  uint32_t m_uiMvdInterTableStride;  uint32_t m_uiMvdCacheAlignedSize;  int32_t m_iUsageType;  int32_t m_iNumDependencyLayers;}; TEST_F (MotionEstimateRangeTest, TestDiamondSearch) {   const int32_t kiMaxBlock16Sad = 72000;//a rough number  uint8_t* pRef = m_pRefStart + PADDING_LENGTH * m_iWidthExt + PADDING_LENGTH;  SWelsFuncPtrList sFuncList;  SWelsME sMe;  SSlice sSlice;  const uint8_t kuiQp = rand() % 52;  InitMe (kuiQp, m_uiMvdInterTableSize, m_uiMvdInterTableStride, m_pMvdCostTable, &sMe);   WelsInitSampleSadFunc (&sFuncList, 0); //test c functions   memset (&sSlice, 0, sizeof (sSlice));  memset (m_pSrc, 128, m_iWidth * m_iHeight);  memset (m_pRefStart, 0, m_iWidthExt * m_iHeightExt);   sMe.uiBlockSize = BLOCK_16x16; //   sMe.sMvp.iMvX = rand() % m_iMvRange;  sMe.sMvp.iMvY = rand() % m_iMvRange;   for (int h = 0; h < m_iHeight; h++)    memset (pRef + h * m_iWidthExt, h, m_iWidthExt);   sMe.pEncMb = m_pSrc;   sMe.sMv.iMvX = sMe.sMvp.iMvX;  sMe.sMv.iMvY = sMe.sMvp.iMvY;   sMe.uiSadCost = sMe.uiSatdCost = kiMaxBlock16Sad;  SetMvWithinIntegerMvRange (m_iMbWidth, m_iMbHeight,     0, 0, m_iMvRange,                             & (sSlice.sMvStartMin), & (sSlice.sMvStartMax));    sMe.pRefMb = pRef + sMe.sMvp.iMvY * m_iWidthExt;  WelsDiamondSearch (&sFuncList, &sMe, &sSlice, m_iWidth, m_iWidthExt);   if ((WELS_ABS (sMe.sMv.iMvX) > m_iMvRange))    printf ("mvx = %d\n", sMe.sMv.iMvX);  ASSERT_TRUE (! (WELS_ABS (sMe.sMv.iMvX) > m_iMvRange));  if ((WELS_ABS (sMe.sMv.iMvY) > m_iMvRange))    printf ("mvy = %d\n", sMe.sMv.iMvY);  ASSERT_TRUE (! (WELS_ABS (sMe.sMv.iMvY) > m_iMvRange));  } TEST_F (MotionEstimateRangeTest, TestWelsMotionCrossSearch) {   SWelsFuncPtrList sFuncList;  SWelsME sMe;  SSlice sSlice;  int32_t iUsageType = 1;  uint8_t* pRef = m_pRefStart + PADDING_LENGTH * m_iWidthExt + PADDING_LENGTH;  const int32_t kiMaxBlock16Sad = 72000;//a rough number   memset (&sSlice, 0, sizeof (sSlice));  memset (&sMe, 0, sizeof (sMe));  WelsInitSampleSadFunc (&sFuncList, 0); //test c functions  WelsInitMeFunc (&sFuncList, 0, iUsageType);   RandomPixelDataGenerator (m_pSrc, m_iWidth, m_iHeight, m_iWidth);  RandomPixelDataGenerator (m_pRefStart, m_iWidthExt, m_iHeightExt, m_iWidthExt);   sMe.uiBlockSize = BLOCK_16x16; //  for (int32_t iMby = 0; iMby < m_iMbHeight; iMby++) {    for (int32_t iMbx = 0; iMbx < m_iMbWidth; iMbx++) {       const uint8_t kuiQp = rand() % 52;       InitMe (kuiQp, m_uiMvdInterTableSize, m_uiMvdInterTableStride, m_pMvdCostTable, &sMe);      SetMvWithinIntegerMvRange (m_iMbWidth, m_iMbHeight, iMbx , iMby, m_iMvRange,                                 & (sSlice.sMvStartMin), & (sSlice.sMvStartMax));        sMe.sMvp.iMvX = rand() % m_iMvRange;      sMe.sMvp.iMvY = rand() % m_iMvRange;      sMe.iCurMeBlockPixX = (iMbx << 4);      sMe.iCurMeBlockPixY = (iMby << 4);      sMe.pRefMb = pRef + sMe.iCurMeBlockPixX + sMe.iCurMeBlockPixY * m_iWidthExt;      sMe.pEncMb = m_pSrc + sMe.iCurMeBlockPixX + sMe.iCurMeBlockPixY * m_iWidth;;      sMe.uiSadCost = sMe.uiSatdCost = kiMaxBlock16Sad;      sMe.pColoRefMb = sMe.pRefMb;      WelsMotionCrossSearch (&sFuncList, &sMe, &sSlice, m_iWidth, m_iWidthExt);      if ((WELS_ABS (sMe.sMv.iMvX) > m_iMvRange))        printf ("mvx = %d\n", sMe.sMv.iMvX);      ASSERT_TRUE (! (WELS_ABS (sMe.sMv.iMvX) > m_iMvRange));      if ((WELS_ABS (sMe.sMv.iMvY) > m_iMvRange))        printf ("mvy = %d\n", sMe.sMv.iMvY);      ASSERT_TRUE (! (WELS_ABS (sMe.sMv.iMvY) > m_iMvRange));    }  }}void MotionEstimateTest::DoLineTest (PLineFullSearchFunc func, bool vertical) {  const int32_t kiMaxBlock16Sad = 72000;//a rough number  SWelsFuncPtrList sFuncList;  SWelsME sMe;   const uint8_t kuiQp = rand() % 52;  InitMe (kuiQp, 648, m_uiMvdTableSize, m_pMvdCostTable, &sMe);   SMVUnitXY sTargetMv;  WelsInitSampleSadFunc (&sFuncList, 0); //test c functions  WelsInitMeFunc (&sFuncList, WelsCPUFeatureDetect (NULL), 1);   uint8_t* pRefPicCenter = m_pRefData + (m_iHeight / 2) * m_iWidth + (m_iWidth / 2);  sMe.iCurMeBlockPixX = (m_iWidth / 2);  sMe.iCurMeBlockPixY = (m_iHeight / 2);   bool bDataGeneratorSucceed = false;  bool bFoundMatch = false;  int32_t iTryTimes = 100;   if (vertical) {    sTargetMv.iMvX = 0;    sTargetMv.iMvY = -sMe.iCurMeBlockPixY + INTPEL_NEEDED_MARGIN + rand() % (m_iHeight - 16 - 2 * INTPEL_NEEDED_MARGIN);  } else {    sTargetMv.iMvX = -sMe.iCurMeBlockPixX + INTPEL_NEEDED_MARGIN + rand() % (m_iWidth - 16 - 2 * INTPEL_NEEDED_MARGIN);    sTargetMv.iMvY = 0;  }  bDataGeneratorSucceed = false;  bFoundMatch = false;  while (!bFoundMatch && (iTryTimes--) > 0) {    if (!YUVPixelDataGenerator (m_pRefData, m_iWidth, m_iHeight, m_iWidth))      continue;     bDataGeneratorSucceed = true;    CopyTargetBlock (m_pSrcBlock, 16, sTargetMv, m_iWidth, pRefPicCenter);     //clean the sMe status    sMe.uiBlockSize = rand() % 5;    sMe.pEncMb = m_pSrcBlock;    sMe.pRefMb = pRefPicCenter;    sMe.pColoRefMb = pRefPicCenter;    sMe.sMv.iMvX = sMe.sMv.iMvY = 0;    sMe.uiSadCost = sMe.uiSatdCost = kiMaxBlock16Sad;    const int32_t iCurMeBlockPixX = sMe.iCurMeBlockPixX;    const int32_t iCurMeBlockQpelPixX = ((iCurMeBlockPixX) << 2);    const int32_t iCurMeBlockPixY = sMe.iCurMeBlockPixY;    const int32_t iCurMeBlockQpelPixY = ((iCurMeBlockPixY) << 2);    uint16_t* pMvdCostX = sMe.pMvdCost - iCurMeBlockQpelPixX - sMe.sMvp.iMvX;	//do the offset here    uint16_t* pMvdCostY = sMe.pMvdCost - iCurMeBlockQpelPixY - sMe.sMvp.iMvY;    uint16_t* pMvdCost = vertical ? pMvdCostY : pMvdCostX;    int iSize = vertical ? m_iHeight : m_iWidth;     //the last selection may be affected by MVDcost, that is when smaller MvY will be better    if (vertical) {      func (&sFuncList, &sMe,            pMvdCost,            m_iMaxSearchBlock, m_iWidth,            INTPEL_NEEDED_MARGIN - sMe.iCurMeBlockPixY,            iSize - INTPEL_NEEDED_MARGIN - 16 - sMe.iCurMeBlockPixY, vertical);      bFoundMatch = (sMe.sMv.iMvX == 0                     && (sMe.sMv.iMvY == sTargetMv.iMvY || abs (sMe.sMv.iMvY) < abs (sTargetMv.iMvY)));    } else {      func (&sFuncList, &sMe,            pMvdCost,            m_iMaxSearchBlock, m_iWidth,            INTPEL_NEEDED_MARGIN - sMe.iCurMeBlockPixX,            iSize - INTPEL_NEEDED_MARGIN - 16 - sMe.iCurMeBlockPixX, vertical);      bFoundMatch = (sMe.sMv.iMvY == 0                     && (sMe.sMv.iMvX == sTargetMv.iMvX || abs (sMe.sMv.iMvX) < abs (sTargetMv.iMvX)));    }    //printf("DoLineTest Target: %d,%d\n", sTargetMv.iMvX, sTargetMv.iMvY);  }  if (bDataGeneratorSucceed) {    //if DataGenerator never succeed, there is no meaning to check iTryTimes    ASSERT_TRUE (iTryTimes > 0);    //it is possible that ref at differnt position is identical, but that should be under a low probability  }} TEST_F (MotionEstimateTest, TestVerticalSearch) {  DoLineTest (LineFullSearch_c, true);}TEST_F (MotionEstimateTest, TestHorizontalSearch) {  DoLineTest (LineFullSearch_c, false);} #ifdef X86_ASMTEST_F (MotionEstimateTest, TestVerticalSearch_SSE41) {  int32_t iTmp = 1;  uint32_t uiCPUFlags = WelsCPUFeatureDetect (&iTmp);  if ((uiCPUFlags & WELS_CPU_SSE41) == 0) return ;   DoLineTest (VerticalFullSearchUsingSSE41, true);} TEST_F (MotionEstimateTest, TestHorizontalSearch_SSE41) {  int32_t iTmp = 1;  uint32_t uiCPUFlags = WelsCPUFeatureDetect (&iTmp);  if ((uiCPUFlags & WELS_CPU_SSE41) == 0) return ;   DoLineTest (HorizontalFullSearchUsingSSE41, false);}#endif class FeatureMotionEstimateTest : public ::testing::Test { public:  virtual void SetUp() {    m_pRefData = NULL;    m_pSrcBlock = NULL;    m_pMvdCostTable = NULL;     m_iWidth = 64;//size of search window    m_iHeight = 64;//size of search window    m_iMaxSearchBlock = 8;    m_uiMvdTableSize	= (1 + (648 << 1));     m_pMa = new CMemoryAlign (16);    ASSERT_TRUE (NULL != m_pMa);    m_pRefData = (uint8_t*)m_pMa->WelsMalloc (m_iWidth * m_iHeight * sizeof (uint8_t), "m_pRefData");    ASSERT_TRUE (NULL != m_pRefData);    m_pSrcBlock = (uint8_t*)m_pMa->WelsMalloc (m_iMaxSearchBlock * m_iMaxSearchBlock * sizeof (uint8_t), "m_pSrcBlock");    ASSERT_TRUE (NULL != m_pSrcBlock);    m_pMvdCostTable = (uint16_t*)m_pMa->WelsMalloc (52 * m_uiMvdTableSize * sizeof (uint16_t), "m_pMvdCostTable");    ASSERT_TRUE (NULL != m_pMvdCostTable);    m_pFeatureSearchPreparation = (SFeatureSearchPreparation*)m_pMa->WelsMalloc (sizeof (SFeatureSearchPreparation),                                  "m_pFeatureSearchPreparation");    ASSERT_TRUE (NULL != m_pFeatureSearchPreparation);    m_pScreenBlockFeatureStorage = (SScreenBlockFeatureStorage*)m_pMa->WelsMalloc (sizeof (SScreenBlockFeatureStorage),                                   "m_pScreenBlockFeatureStorage");    ASSERT_TRUE (NULL != m_pScreenBlockFeatureStorage);  }  virtual void TearDown() {    if (m_pMa) {      if (m_pRefData) {        m_pMa->WelsFree (m_pRefData, "m_pRefData");        m_pRefData = NULL;      }      if (m_pSrcBlock) {        m_pMa->WelsFree (m_pSrcBlock, "m_pSrcBlock");        m_pSrcBlock = NULL;      }      if (m_pMvdCostTable) {        m_pMa->WelsFree (m_pMvdCostTable, "m_pMvdCostTable");        m_pMvdCostTable = NULL;      }       if (m_pFeatureSearchPreparation) {        ReleaseFeatureSearchPreparation (m_pMa, m_pFeatureSearchPreparation->pFeatureOfBlock);        m_pMa->WelsFree (m_pFeatureSearchPreparation, "m_pFeatureSearchPreparation");        m_pFeatureSearchPreparation = NULL;      }      if (m_pScreenBlockFeatureStorage) {        ReleaseScreenBlockFeatureStorage (m_pMa, m_pScreenBlockFeatureStorage);        m_pMa->WelsFree (m_pScreenBlockFeatureStorage, "m_pScreenBlockFeatureStorage");        m_pScreenBlockFeatureStorage = NULL;      }      delete m_pMa;      m_pMa = NULL;    }  }  void InitRefPicForMeTest (SPicture* pRefPic) {    pRefPic->pData[0] = m_pRefData;    pRefPic->iLineSize[0] = m_iWidth;    pRefPic->iFrameAverageQp = rand() % 52;    pRefPic->iWidthInPixel = m_iWidth;    pRefPic->iHeightInPixel = m_iHeight;  } public:  CMemoryAlign* m_pMa;   SFeatureSearchPreparation* m_pFeatureSearchPreparation;  SScreenBlockFeatureStorage* m_pScreenBlockFeatureStorage;   uint8_t* m_pRefData;  uint8_t* m_pSrcBlock;  uint16_t* m_pMvdCostTable;  uint32_t m_uiMvdTableSize;   int32_t m_iWidth;  int32_t m_iHeight;  int32_t m_iMaxSearchBlock;}; TEST_F (FeatureMotionEstimateTest, TestFeatureSearch) {  const int32_t kiMaxBlock16Sad = 72000;//a rough number  SWelsFuncPtrList sFuncList;  WelsInitSampleSadFunc (&sFuncList, 0); //test c functions  WelsInitMeFunc (&sFuncList, 0, true);   SWelsME sMe;  const uint8_t kuiQp = rand() % 52;  InitMe (kuiQp, 648, m_uiMvdTableSize, m_pMvdCostTable, &sMe);  sMe.iCurMeBlockPixX = (m_iWidth / 2);  sMe.iCurMeBlockPixY = (m_iHeight / 2);  uint8_t* pRefPicCenter = m_pRefData + (m_iHeight / 2) * m_iWidth + (m_iWidth / 2);   SPicture sRef;  InitRefPicForMeTest (&sRef);   SSlice sSlice;  const int32_t kiSupposedPaddingLength = 16;  SetMvWithinIntegerMvRange (m_iWidth / 16 - kiSupposedPaddingLength, m_iHeight / 16 - kiSupposedPaddingLength,                             m_iWidth / 2 / 16, m_iHeight / 2 / 16, 508,                             & (sSlice.sMvStartMin), & (sSlice.sMvStartMax));  int32_t iReturn;  const int32_t kiNeedFeatureStorage = ME_DIA_CROSS_FME;  iReturn = RequestFeatureSearchPreparation (m_pMa, m_iWidth,  m_iHeight, kiNeedFeatureStorage,

Lines 1–500 of 565.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t