Начинаю выкладывать занятные статьи с ps2dev
--------------------------------------------------------------------------------------
Попробую немножко просветить по поводу GE процессора , и минимальной обвязки для работы без sceGu библиотеки.
Текста писать много не буду, лучше на примере.
1. Инициализация GE
Перехват VBlankHandler
Установка callbacks от GE процессора
SceVoid GE_Initialize()
{
  sceDisplaySetVblankCallback(1,GE_DisplayVBlank,SCE_NULL);
  SceGeCbParam cb_param;
  cb_param.pSignalFunc   = GE_Signal;
  cb_param.pSignalCookie = SCE_NULL;
  cb_param.pFinishFunc   = GE_Finish;
  cb_param.pFinishCookie = SCE_NULL;
  dsEngine::GE.SetInterruptID(sceGeSetCallback(&cb_param));
}
Теперь рассмотрим сам VBlankHandler
BEGIN_CRITICAL,END_CRITICAL - это семафорная обвязка. (sceKernelWaitSema,sceKernelSignalSema)
здесь происходит установка нового front буффера , также подсчитываеться FPS
/// VBlank Interrupt
SceVoid dsEngine::dsGeCore::VBlankInterrupt(SceUInt32 )
{
  BEGIN_CRITICAL();
  {
    m_VBlankCount++;
    if (m_DrawLock == SCE_TRUE)
    {
      END_CRITICAL();
      return;
    }
  }
  END_CRITICAL();
  
  UpdateCommandBufferState();
  BEGIN_CRITICAL();
  {
    if (m_Locked == SCE_FALSE && m_RequestFlip == SCE_TRUE)
    {
      m_FlipFrameBufferCount++;
      sceDisplaySetFrameBuf(m_FlipFrameBufferAddress,512,
        SCE_DISPLAY_PIXEL_RGBA8888,SCE_DISPLAY_UPDATETIMING_NEXTHSYNC);
      m_CurrFrontBufferAddress = m_FlipFrameBufferAddress;
      m_RequestFlip = SCE_FALSE;
    }
    else
    {
      profile.missedVBlankCount++;
    }
    if (m_VBlankCount > 60)
    {
      m_fCurrentFPS = (static_cast<SceFloat32>(m_FlipFrameBufferCount) * 60.0f) /
         static_cast<SceFloat32>(m_VBlankCount);
      m_VBlankCount = 0;
      m_FlipFrameBufferCount = 0;
    }
  }
  END_CRITICAL();
  UpdateCommandBufferState();
}
Теперь самое интересное (UpdateCommandBufferState)
Это как раз display double buffer list.
Непосредственная работа с GE
/// Обновление статусов command push buffer.
/// проверка на окончание отрисовки, и проверка на 
/// отсылку нового command push buffer
SceVoid dsEngine::dsGeCore::UpdateCommandBufferState()
{
  BEGIN_CRITICAL();
  {
    // если этот командный буффер уже отослан на исполнение,
    // тогда проверяем статус его отрисовки.
    if (m_CommandBufferStatus[m_DrawCommandBufferIdx] == CMDBUFFSTATUS_QUEUED)
    {
      // если список уже отрисовался
      if (sceGeDrawSync(1) == SCE_GE_LIST_COMPLETED)
      {
        // если нет запроса на флип front/back буффер,
        // завершаем данный список и просим сделать флип на
        // следующем vblank interrupt
        if (m_RequestFlip == SCE_FALSE)
        {
          // запоминаем новый адрес frame buffer для флипа и делаем запрос на флип.
          m_FlipFrameBufferAddress = static_cast<SceUInt32*>
            (m_CommandBufferFrameAddress[m_DrawCommandBufferIdx]);
          m_RequestFlip            = SCE_TRUE;
          // освобождаем command push buffer
          m_CommandBufferStatus[m_DrawCommandBufferIdx] = CMDBUFFSTATUS_FREE;
          m_DrawCommandBufferIdx = 1 - m_DrawCommandBufferIdx;
        }
      }
    }
    // если коммандный буффер подготовлен к отcылке, отправляем его на GE
    if (m_Locked == SCE_FALSE && 
        m_CommandBufferStatus[m_DrawCommandBufferIdx] == CMDBUFFSTATUS_READY)
    {
      if (m_RequestFlip == SCE_FALSE)
      {
        profile.startGPUTime = sceKernelGetSystemTimeLow();
        m_CommandBufferStatus[m_DrawCommandBufferIdx] = CMDBUFFSTATUS_QUEUED;
        m_QueueDispListId[m_DrawCommandBufferIdx] = sceGeListEnQueue
        (
          m_CommandBuffer[m_DrawCommandBufferIdx],
          SCE_NULL,
          m_InterruptID,
          SCE_NULL
        );
      }
    }
  }
  END_CRITICAL();
}
Все что осталось подсмотреть, это реализация callbacks GE
CMD_FINISH
SceVoid dsEngine::dsGeCore::FinishCallback(SceUInt32 )
{
  profile.currentGPUTime = sceKernelGetSystemTimeLow() - profile.startGPUTime;
  UpdateCommandBufferState();
}
CMD_SIGNAL
static SceVoid GE_Signal(SceInt32 interruptCode,SceVoid* cookie,const SceVoid* memoryAddress)
{
  register SceUShort16 intrCode = static_cast<SceUShort16>(interruptCode);
  // внутренние GE library сообщения
  if ((intrCode & 0xf000) == 0xf000)
  {
    // сохранения контекста
    if (intrCode == GECORE_SIGNAL_STORECONTEXT)
    {
      sceGeSaveContext(&geContext);
    }
    // восстановление контекста
    else if (intrCode == GECORE_SIGNAL_RESTORECONTEXT)
    {
      sceGeRestoreContext(&geContext);
    }
    // изменение ширины трансляции адресов GE процессора
    else if (intrCode == GECORE_SIGNAL_SETADDRESSTRANSLATION)
    {
      sceGeEdramSetAddrTranslation(sceGeGetCmd(SCE_GE_CMD_END)&0xFFFF);
    }
    // системные SIGNAL идут через паузу в GE
    // поэтому продолжаем работу GE процессора.
    sceGeContinue();
  }
  else
  {
    if (geSignalCallback != SCE_NULL)
    {
      geSignalCallback(intrCode);
    }
  }
}
Работа с COMMAND PUSH BUFFER
Делаем такую функцию в .h
inline SceVoid GE_PUTCOMMAND(SceUInt32 command)
{
  *dsEngine::geCmdBuffer++ = command;
}
теперь пример заполнения PUSH BUFFER
static SceVoid GECORE_SetViewport()
{
  SceFloat32 sx = static_cast<SceFloat32>( geDisplayWidth  / 2);
  SceFloat32 sy = static_cast<SceFloat32>(-geDisplayHeight / 2);
  SceFloat32 tx = 2048.0f;
  SceFloat32 ty = 2048.0f;
  GE_PUTCOMMAND(SCE_GE_SET_SX_FLOAT24(sx));
  GE_PUTCOMMAND(SCE_GE_SET_SY_FLOAT24(sy));
  GE_PUTCOMMAND(SCE_GE_SET_TX_FLOAT24(tx));
  GE_PUTCOMMAND(SCE_GE_SET_TY_FLOAT24(ty));
}
.....
SceVoid GECORE_SetDepthRange(SceInt32 depthMin,SceInt32 depthMax)
{
  GE_PUTCOMMAND(SCE_GE_SET_MINZ(depthMin));
  GE_PUTCOMMAND(SCE_GE_SET_MAXZ(depthMax));
  SceFloat32 fDepthMin = static_cast<SceFloat32>(depthMin);
  SceFloat32 fDepthMax = static_cast<SceFloat32>(depthMax);
  SceFloat32 sz = (fDepthMax - fDepthMin) * 0.5f;
  SceFloat32 tz = (fDepthMax + fDepthMin) * 0.5f;
  GE_PUTCOMMAND(SCE_GE_SET_SZ_FLOAT24(sz));
  GE_PUTCOMMAND(SCE_GE_SET_TZ_FLOAT24(tz));
}
....
BeginFrame/EndFrame
В BeginFrame, показанными выше коммандами делаем DefaultRenderState
Более интересен EndFrame
EndFrame
1. Подводим статистику для профилирования (эмуляция perfhud 

 )
  и запоминаем значения статистики (например последних 128 значений), что бы выводить графики CPU,GPU выполнения отдельных GE list , etc....
2. Отрисовка интерфейса профилирования
И в конце
  geCmdBuffer       = SCE_NULL;
  m_geCmdBufferStart  = SCE_NULL;
  BEGIN_CRITICAL();
  {
    m_CommandBufferStatus[m_FillCommandBufferIdx] = CMDBUFFSTATUS_READY;
    if (m_CommandBufferStatus[1 - m_FillCommandBufferIdx] == CMDBUFFSTATUS_FREE)
    {
      m_DrawCommandBufferIdx = m_FillCommandBufferIdx;
    }
  }
  END_CRITICAL();
  m_bFrontBufferValid = SCE_FALSE;
  UpdateCommandBufferState();
  m_FillCommandBufferIdx = 1 - m_FillCommandBufferIdx;
В BeginFrame соотв. будет
  geCmdBuffer = reinterpret_cast<SceUInt32*>
    (0x40000000 | reinterpret_cast<SceUInt32>(m_CommandBuffer[m_FillCommandBufferIdx]));
  m_geCmdBufferStart  = geCmdBuffer;