Отображение трёхмерной графики на PSP

Пару месяцев назад я вновь достал из ящика запылившуюся PSP и решил портировать туда мой ранее уже показанный движок. С программной отрисовкой проблем не возникло – всё и так работает. А вот с использованием GU всё оказалось не так просто. В данной статье я покажу на примере, как можно написать для PSP простое трёхмерное приложение, использующее GU.

Заранее предупреждаю, что руководств по программированию для PSP довольно мало, и поэтому какие-то мои выводы могут оказаться неверными. Но, к делу.

Главная функция программы для PSP, если кто не знает, оформляется примерно вот так:

#include <pspkernel.h>
#include <pspdebug.h>
#include <pspdisplay.h>

//----------------------------------------------------------------------------------------
PSP_MODULE_INFO("GUTexture", 0, 1, 1);
PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER|THREAD_ATTR_VFPU);

void dump_threadstatus(void);

bool done=false;

int exit_callback(int arg1,int arg2,void *common)
{
 done=true;
 return(0);
}

int CallbackThread(SceSize args, void *argp)
{
 int cbid;
 cbid=sceKernelCreateCallback("Exit Callback",exit_callback,NULL);
 sceKernelRegisterExitCallback(cbid);
 sceKernelSleepThreadCB();
 return(0);
}
int SetupCallbacks(void)
{
 int thid = 0;
 thid=sceKernelCreateThread("update_thread",CallbackThread,0x11,0xFA0,0,0);
 if(thid>=0) sceKernelStartThread(thid, 0, 0);
 return(thid);
}
//----------------------------------------------------------------------------------------
//начинаем программу
//----------------------------------------------------------------------------------------

int main(int argc, char  **argv)
{
 pspDebugScreenInit();
 //устанавливаем обработчики
 SetupCallbacks();
 //выполняем программу 
 ……….
 //выходим из программы
 sceKernelExitGame();
 return(0);
}

Инициализация GU выполняется следующим образом:

Сначала мы запрашиваем указатели на три буфера – экранный, внеэкранный и буфер глубины (Z-буфер). Буферы выравниваются по 512 пикселей в строке (хотя у PSP строка 480 пикселей). Также требуется учесть формат цвета пикселя. В данном примере использован формат GU_PSM_8888 — по 8 бит на R,G,B и Alpha-компоненты цвета пикселя. Для Z-буффера использован формат GU_PSM_4444 просто потому что это 16 бит — Z-буфер у PSP 16 битный.

//размеры экрана
#define SCREEN_WIDTH 480
#define SCREEN_HEIGHT 272
#define SCREEN_LINE_WIDTH 512

void* fbp0=getStaticVramBuffer(SCREEN_LINE_WIDTH, SCREEN_HEIGHT,GU_PSM_8888);
void* fbp1=getStaticVramBuffer(SCREEN_LINE_WIDTH, SCREEN_HEIGHT,GU_PSM_8888);
void* zbp=getStaticVramBuffer(SCREEN_LINE_WIDTH, SCREEN_HEIGHT,GU_PSM_4444);

Функция запроса указателей на буферы определяется как

#include <pspge.h>
#include <pspgu.h>

static unsigned int staticOffset=0;

static unsigned int getMemorySize(unsigned int width,unsigned int height,unsigned int psm)
{
 switch (psm)
 {
  case GU_PSM_T4: return((width*height)>>1);
  case GU_PSM_T8: return(width*height);
  case GU_PSM_5650:
  case GU_PSM_5551:
  case GU_PSM_4444:
  case GU_PSM_T16: return(2*width*height);
  case GU_PSM_8888:
  case GU_PSM_T32: return(4*width*height);
  default: return(0);
 }
}

void* getStaticVramBuffer(unsigned int width,unsigned int height,unsigned int psm)
{
 unsigned int memSize=getMemorySize(width,height,psm);
 void* result=(void*)staticOffset;
 staticOffset+=memSize;
 return(result);
}

void* getStaticVramTexture(unsigned int width,unsigned int height,unsigned int psm)
{
 void* result=getStaticVramBuffer(width,height,psm);
 return((void*)(((unsigned int)result) + ((unsigned int)sceGeEdramGetAddr())));
}

Это не мои функции – я их взял из какой-то программы давным-давно и лишь слегка изменил. Память распределяется в области видеопамяти. Текстуры также следует по возможности размещать там же, запрашивая указатель через getStaticVramTexture, иначе быстродействие резко упадёт. Разумеется, никакой динамической памяти при таких запросах не выделяется, а просто распределяется часть заданного адресного пространства PSP под экран и текстуры. Насколько я помню, видеопамяти у PSP всего 2 мегабайта — это очень мало для хранения множества текстур.

Программирование GU у PSP похоже на программирование для OpenGL с одним отличием — выполнение команд требует их размещения в дисплейном списке, причём память для этого списка должна быть заранее выделена и при этом выровнена:
static unsigned char __attribute__((aligned(16))) DisplayList[262144];
Команды, относящиеся к преобразованию координат не требуют дисплейного списка и могут выполняться в любом месте программы.

Инициализировать GU можно, например, так:

//размер сторон виртуального экрана PSP
#define VIRTUAL_SCREEN_SIZE 2048
//соотношение сторон экрана
#define SCREEN_ASPECT 16.0f/9.0f
//передняя плоскость отсечения
#define NEAR_PLANE_Z 5.0f
//задняя плоскость отсечения
#define FAR_PLANE_Z 4096.0f
//угол зрения
#define EYE_ANGLE 60.0f

//инициализируем графику GU
 sceGuInit();
 //создаём и запускаем на выполнение новый контекст дисплея - он должен выполниться сразу, т.к. GU_DIRECT
 sceGuStart(GU_DIRECT,DisplayList);
 //устанавливаем параметры буфера рисования- формат пикселя, указатель на область видеопамяти, длину строки (выровненную, а не физическую)
 sceGuDrawBuffer(GU_PSM_8888,fbp0,SCREEN_LINE_WIDTH);
 //устанавливаем параметры буфера экрана - размер экрана, указатель на видеопамять, длину строки
 sceGuDispBuffer(SCREEN_WIDTH,SCREEN_HEIGHT,fbp1,SCREEN_LINE_WIDTH);
 //устанавливаем параметры буфера глубины- указатель на начало буфера глубины в видеопамяти и длину строки
 sceGuDepthBuffer(zbp,SCREEN_LINE_WIDTH);
 //устанавливаем смещение экрана в общем пространстве 4096x4096 (в PSP такой размер виртуального экрана)
 sceGuOffset(VIRTUAL_SCREEN_SIZE-(SCREEN_WIDTH/2),VIRTUAL_SCREEN_SIZE-(SCREEN_HEIGHT/2));//ставим по центру
 //настраиваем видовой порт - порт просмотра- координаты центра и размеры сторон
 sceGuViewport(VIRTUAL_SCREEN_SIZE,VIRTUAL_SCREEN_SIZE,SCREEN_WIDTH,SCREEN_HEIGHT);
 //устанавливаем диапазон значений для буфера глубины - передняя и задняя плоскости отсечения (буфер инвертирован и значения от 0 до 65535 !)
 sceGuDepthRange(65535,0);
 //включаем обрезание области показа по размерам видового порта
 sceGuScissor(0,0,SCREEN_WIDTH,SCREEN_HEIGHT);
 sceGuEnable(GU_SCISSOR_TEST);
 sceGuEnable(GU_CLIP_PLANES);
  //настроим матрицу проецирования
 sceGumMatrixMode(GU_PROJECTION);
 sceGumLoadIdentity();
 sceGumPerspective(EYE_ANGLE,SCREEN_ASPECT,NEAR_PLANE_Z,FAR_PLANE_Z);
 //включим режим гладкой интерполяции цвета граней
 sceGuShadeModel(GU_SMOOTH);
 //включим тест глубины
 sceGuDepthFunc(GU_GEQUAL);
 sceGuEnable(GU_DEPTH_TEST);
 sceGuDepthMask(GU_FALSE);
 //отключим режим отсечения граней, повёрнутых обратной стороной к наблюдателю
 sceGuFrontFace(GU_CCW);
 sceGuDisable(GU_CULL_FACE);
 //настраиваем прозрачность
 sceGuDisable(GU_BLEND);
 sceGuBlendFunc(GU_ADD,GU_SRC_ALPHA,GU_ONE_MINUS_SRC_ALPHA,0,0);
 //выполняем созданный список
 sceGuFinish();
 sceGuSync(GU_SYNC_WAIT,GU_SYNC_FINISH);
 sceGuDisplay(GU_TRUE); 

После завершения работы с GU следует вызвать sceGuTerm().

После загрузки текстуры размера (WidthImage; HeightImage) любым удобным способом (указатель Data на данные текстуры – и лучше его получать в области видеопамяти), мы можем её вывести на экран.

  //рисуем сцену
  sceGuStart(GU_DIRECT,DisplayList);
  //очистим экран и буфер глубины
  sceGuClearColor(0);
  sceGuClearDepth(0);
  sceGuClear(GU_COLOR_BUFFER_BIT|GU_DEPTH_BUFFER_BIT);

  //настроим матрицу проецирования
  sceGumMatrixMode(GU_PROJECTION);
  sceGumLoadIdentity();
  sceGumPerspective(EYE_ANGLE,SCREEN_ASPECT,NEAR_PLANE_Z,FAR_PLANE_Z);
  sceGumUpdateMatrix();.

  //инициализируем матрицы
  sceGumMatrixMode(GU_TEXTURE);
  sceGumLoadIdentity();
  sceGumMatrixMode(GU_VIEW);
  sceGumLoadIdentity();
  sceGumMatrixMode(GU_MODEL);
  sceGumLoadIdentity();

  //выводим прямоугольник с текстурой
  sceGuColor(0xffffffff);//цвет окраски
  sceGuEnable(GU_TEXTURE_2D);
  sceGuTexMode(GU_PSM_8888,0,0,0);
  sceGuTexImage(0,WidthImage,HeightImage,WidthImage,Data);
  sceGuTexFunc(GU_TFX_MODULATE,GU_TCC_RGBA);
  sceGuTexFilter(GU_NEAREST,GU_NEAREST);
  sceGuTexWrap(GU_REPEAT,GU_REPEAT);
  sceGuTexScale(1,1);
  sceGuTexOffset(0,0);
 
  //выводим полигон по точкам из массива
  …
    
  sceGuDisable(GU_TEXTURE_2D);
  //запускаем список на выполнение
  sceGuFinish();
  sceGuSync(GU_SYNC_WAIT,GU_SYNC_FINISH);
  //делаем видимым буфер, в котором мы рисовали
  sceDisplayWaitVblankStart();
  sceGuSwapBuffers();

Как вывести полигон? Для рисования геометрии GU у PSP просит поместить все точки в массив, указатель на который нужно предварительно получить командой sceGuGetMemory, передав ей размер запрашиваемого блока памяти в байтах. Дальше по этому указателю вы должны записать массив точек и попросить PSP их вывести, например, командой sceGumDrawArray с нужными параметрами. Но каков формат этих точек? У PSP данные точек располагаются в определённом порядке и размер массива, описывающего одну точку должен быть кратен 32 байтам: Вес вершины, текстурные координаты, цвет точки, нормаль к точке, координата точки. Именно в таком порядке. Чтобы не заморачиваться с форматом, я определил набор структур и функций для работы с ними:

//#pragma pack(1)
//[for vertices(1-8)] [weights (0-8)] [texture uv] [color] [normal] [vertex] [/for]

#pragma pack(1)

//координата точки
struct SGuVertex
{
 float X;
 float Y;
 float Z;
};
//нормаль к точке
struct SGuNormal
{
 float Nx;
 float Ny;
 float Nz;
};
//текстурные координаты
struct SGuTexture
{
 float U;
 float V;
};
//цвет точки
struct SGuColor
{
 unsigned long Color;
};
#pragma pack()

#pragma pack(32)
//точка с текстурой, цветом, нормалью, координатами
struct SGuNVCTPoint
{
 SGuTexture sGuTexture;
 SGuColor sGuColor;
 SGuNormal sGuNormal;
 SGuVertex sGuVertex;
};
#pragma pack()

  void SetVertexCoord(SGuVertex &sGuVertex,float x,float y,float z);//задать координаты вершины
  void SetNormalCoord(SGuNormal &sGuNormal,float nx,float ny,float nz);//задать координаты нормали
  void SetTextureCoord(SGuTexture &sGuTexture,float u,float v);//задать координаты текстуры
  void SetColorValue(SGuColor &sGuColor,unsigned long color);//задать цвет

//----------------------------------------------------------------------------------------------------
//задать координаты вершины
//----------------------------------------------------------------------------------------------------
void CMain::SetVertexCoord(SGuVertex &sGuVertex,float x,float y,float z)
{
 sGuVertex.X=x;
 sGuVertex.Y=y;
 sGuVertex.Z=z;
}
//----------------------------------------------------------------------------------------------------
//задать координаты нормали
//----------------------------------------------------------------------------------------------------
void CMain::SetNormalCoord(SGuNormal &sGuNormal,float nx,float ny,float nz)
{
 sGuNormal.Nx=nx;
 sGuNormal.Ny=ny;
 sGuNormal.Nz=nz;
}
//----------------------------------------------------------------------------------------------------
//задать координаты текстуры
//----------------------------------------------------------------------------------------------------
void CMain::SetTextureCoord(SGuTexture &sGuTexture,float u,float v)
{
 sGuTexture.U=u;
 sGuTexture.V=v;
}
//----------------------------------------------------------------------------------------------------
//задать цвет
//----------------------------------------------------------------------------------------------------
void CMain::SetColorValue(SGuColor &sGuColor,unsigned long color)
{
 sGuColor.Color=color;
}

Тогда задать геометрию (в данном случае — квадрат) можно, например, так:

  //задаём геометрию
  SGuNVCTPoint sGuNVCTPoint;
  vector<SGuNVCTPoint> vector_point;

  SetVertexCoord(sGuNVCTPoint.sGuVertex,-100,100,0);
  SetTextureCoord(sGuNVCTPoint.sGuTexture,0,0);
  SetNormalCoord(sGuNVCTPoint.sGuNormal,0,0,1);
  SetColorValue(sGuNVCTPoint.sGuColor,0xFFFFFFFF);
  vector_point.push_back(sGuNVCTPoint);

  SetVertexCoord(sGuNVCTPoint.sGuVertex,100,100,0);
  SetTextureCoord(sGuNVCTPoint.sGuTexture,1,0);
  SetNormalCoord(sGuNVCTPoint.sGuNormal,0,0,1);
  SetColorValue(sGuNVCTPoint.sGuColor,0xFFFFFFFF);
  vector_point.push_back(sGuNVCTPoint);

  SetVertexCoord(sGuNVCTPoint.sGuVertex,100,-100,0);
  SetTextureCoord(sGuNVCTPoint.sGuTexture,1,1);
  SetNormalCoord(sGuNVCTPoint.sGuNormal,0,0,1);
  SetColorValue(sGuNVCTPoint.sGuColor,0xFFFFFFFF);
  vector_point.push_back(sGuNVCTPoint);

  SetVertexCoord(sGuNVCTPoint.sGuVertex,-100,-100,0);
  SetTextureCoord(sGuNVCTPoint.sGuTexture,0,1);
  SetNormalCoord(sGuNVCTPoint.sGuNormal,0,0,1);
  SetColorValue(sGuNVCTPoint.sGuColor,0xFFFFFFFF);
  vector_point.push_back(sGuNVCTPoint);

А вывести его, например, так:

  size_t vertex_amount=vector_point.size();
  SGuNVCTPoint *sGuNVCTPoint_Ptr=(SGuNVCTPoint*)sceGuGetMemory(vertex_amount*sizeof(SGuNVCTPoint));
  if (sGuNVCTPoint_Ptr!=NULL)
  {
   for(size_t n=0;n<vertex_amount;n++) sGuNVCTPoint_Ptr[n]=vector_point[n];
   sceGumDrawArray(GU_TRIANGLE_FAN,GU_COLOR_8888|GU_VERTEX_32BITF|GU_TRANSFORM_3D|GU_NORMAL_32BITF|GU_TEXTURE_32BITF,vertex_amount,0,sGuNVCTPoint_Ptr);
  }

Для вывода я указал функции sceGumDrawArray, что именно я рисую и каков формат точки ( GU_COLOR_8888|GU_VERTEX_32BITF|GU_TRANSFORM_3D|GU_NORMAL_32BITF|GU_TEXTURE_32BITF — точка состоит из цвета, координат, нормали, текстурных координат и требует перемножения координат на соответствующие матрицы перед рисованием). Рисование возможно только треугольниками. Но это ещё не всё…

Вроде бы всё работает, но работает только, если все точки находятся перед глазами и видимы. Стоит хотя бы одной точке уйти в какую-то туманную даль, как GU отказывается рисовать весь многоугольник. Как я понимаю, GU у PSP требует, чтобы относительно четырёх плоскостей отсечения (левая, правая, верхняя и нижняя (а передняя получится автоматически)) точка лежала внутри этого объёма, иначе GU не согласен её выводить. Проблема. Но в играх-то 3D-графика присутствует и таких артефактов не наблюдается! Давайте посмотрим, как решили эту проблему в PSP Quake 1, благо исходники доступны для анализа.

Что же мы видим из анализа исходников? А по сути вот что:

  //получаем матрицу проецирования
  sceGumMatrixMode(GU_PROJECTION);
  ScePspFMatrix4 projection_matrix;
  sceGumStoreMatrix(&projection_matrix);
  //получаем матрицу видового преобразования
  sceGumMatrixMode(GU_VIEW);
  ScePspFMatrix4 view_matrix;
  sceGumStoreMatrix(&view_matrix);
  //получаем матрицу моделирования
  sceGumMatrixMode(GU_MODEL);
  ScePspFMatrix4 model_matrix;
  sceGumStoreMatrix(&model_matrix);
  sceGuFinish();

  //вычисляем общую матрицу view-projection
  ScePspFMatrix4 projection_view_matrix;
  MultiplyScePspFMatrix4(view_matrix,projection_matrix,projection_view_matrix);
  //вычисляем общую матрицу view-projection-model
  ScePspFMatrix4 projection_view_model_matrix;
  MultiplyScePspFMatrix4(model_matrix,projection_view_matrix,projection_view_model_matrix);
  //вычисляем матрицу view-model
  ScePspFMatrix4 view_model_matrix;
  MultiplyScePspFMatrix4(model_matrix,view_matrix,view_model_matrix);
  //вычисляем четыре плоскости отсечения по проекции (верх, низ, лево, право)
  ScePspFVector4 frustum[4];//четверка чисел описывает плоскость: ax+by+cz+d=0
  //левая
  frustum[0].x=projection_view_model_matrix.x.w+projection_view_model_matrix.x.x;
  frustum[0].y=projection_view_model_matrix.y.w+projection_view_model_matrix.y.x;
  frustum[0].z=projection_view_model_matrix.z.w+projection_view_model_matrix.z.x;
  frustum[0].w=projection_view_model_matrix.w.w+projection_view_model_matrix.w.x;
  NormaliseScePspFVector4(frustum[0]);
  //правая
  frustum[1].x=projection_view_model_matrix.x.w-projection_view_model_matrix.x.x;
  frustum[1].y=projection_view_model_matrix.y.w-projection_view_model_matrix.y.x;
  frustum[1].z=projection_view_model_matrix.z.w-projection_view_model_matrix.z.x;
  frustum[1].w=projection_view_model_matrix.w.w-projection_view_model_matrix.w.x;
  NormaliseScePspFVector4(frustum[1]);
  //верхняя
  frustum[2].x=projection_view_model_matrix.x.w-projection_view_model_matrix.x.y;
  frustum[2].y=projection_view_model_matrix.y.w-projection_view_model_matrix.y.y;
  frustum[2].z=projection_view_model_matrix.z.w-projection_view_model_matrix.z.y;
  frustum[2].w=projection_view_model_matrix.w.w-projection_view_model_matrix.w.y;
  NormaliseScePspFVector4(frustum[2]);
  //нижняя
  frustum[3].x=projection_view_model_matrix.x.w+projection_view_model_matrix.x.y;
  frustum[3].y=projection_view_model_matrix.y.w+projection_view_model_matrix.y.y;
  frustum[3].z=projection_view_model_matrix.z.w+projection_view_model_matrix.z.y;
  frustum[3].w=projection_view_model_matrix.w.w+projection_view_model_matrix.w.y;
  NormaliseScePspFVector4(frustum[3]);

То есть, в Quake 1 перед выводом просто переносят все точки внутрь объёма, ограничивающего взгляд, либо выбрасывают их вовсе (если вся фигура не видна). Как же это сделать? Нужно просто считать три матрицы — GU_PROJECTION, GU_MODEL, GU_VIEW. Перемножить их и получить итоговую матрицу преобразования координат. Из этой матрицы можно вытащить все нужные ограничивающие вид плоскости (4 компоненты полученного вектора задают плоскость с уравнением ax+by+cz+w=0). (a,b,c) — вектор нормали, а w=a*x0+b*y0+c*z0 — характеризует некую точку (x0,y0,z0) плоскости. Сами координаты точки нам не нужны — достаточно знать w.

Отсечение выполняется следующим образом (для четырёх вышеуказанных плоскостей по-очереди в цикле):

  //выполняем отсечение
  vector<SGuNVCTPoint> vector_clip_point;
  for(long n=0;n<4;n++)
  {
   float nx=frustum[n].x;
   float ny=frustum[n].y;
   float nz=frustum[n].z;
   float w=frustum[n].w;

   Clip(vector_point,vector_clip_point,nx,ny,nz,w);
   vector_point=vector_clip_point;
  }

Но для такого фокуса нам потребуются следующие функции (списанные из Quake 1):

//----------------------------------------------------------------------------------------------------
//получить точку пересечения прямой и плоскости
//----------------------------------------------------------------------------------------------------
void CMain::GetIntersectionPlaneAndLine(const SGuNVCTPoint& A,const SGuNVCTPoint& B,SGuNVCTPoint& new_point,float nx,float ny,float nz,float w)
{
 new_point=A;
 float ax=A.sGuVertex.X;
 float ay=A.sGuVertex.Y;
 float az=A.sGuVertex.Z;
 float au=A.sGuTexture.U;
 float av=A.sGuTexture.V;

 float bx=B.sGuVertex.X;
 float by=B.sGuVertex.Y;
 float bz=B.sGuVertex.Z;
 float bu=B.sGuTexture.U;
 float bv=B.sGuTexture.V;

 float dx=bx-ax;
 float dy=by-ay;
 float dz=bz-az;
 float du=bu-au;
 float dv=bv-av;

 float top=(nx*ax)+(ny*ay)+(nz*az)+w;
 float bottom=(nx*dx)+(ny*dy)+(nz*dz);
 float time=-top/bottom;

 float vx=ax+time*dx;
 float vy=ay+time*dy;
 float vz=az+time*dz;
 float vu=au+time*du;
 float vv=av+time*dv;
 //добавляем новую точку
 SetVertexCoord(new_point.sGuVertex,vx,vy,vz);
 SetTextureCoord(new_point.sGuTexture,vu,vv);
}
//----------------------------------------------------------------------------------------------------
//выполнить коррекцию координат
//----------------------------------------------------------------------------------------------------
void CMain::Clip(const vector<SGuNVCTPoint>& vector_point_input,vector<SGuNVCTPoint>& vector_point_output,float nx,float ny,float nz,float w)
{
 vector_point_output.clear();
 long point=vector_point_input.size();
 for(long n=0;n<point;n++)
 {
  long next_p=n+1;
  if (next_p>=point) next_p-=point;

  const SGuNVCTPoint *sGuNVCTPoint_Current_Ptr=&(vector_point_input[n]);
  float current_vx=sGuNVCTPoint_Current_Ptr->sGuVertex.X;
  float current_vy=sGuNVCTPoint_Current_Ptr->sGuVertex.Y;
  float current_vz=sGuNVCTPoint_Current_Ptr->sGuVertex.Z;
  //определяем положение относительно плоскости отсечения
  float current_ret=current_vx*nx+current_vy*ny+current_vz*nz+w;

  const SGuNVCTPoint *sGuNVCTPoint_Next_Ptr=&(vector_point_input[next_p]);
  float next_vx=sGuNVCTPoint_Next_Ptr->sGuVertex.X;
  float next_vy=sGuNVCTPoint_Next_Ptr->sGuVertex.Y;
  float next_vz=sGuNVCTPoint_Next_Ptr->sGuVertex.Z;
  //определяем положение относительно плоскости отсечения
  float next_ret=next_vx*nx+next_vy*ny+next_vz*nz+w;

  if (current_ret>0)//текущая точка видима
  {
   if (next_ret>0)//следующая точка видима
   {
    vector_point_output.push_back(*sGuNVCTPoint_Next_Ptr);
   }
   else
   {
    //добавляем новую точку пересечения
    SGuNVCTPoint sGuNVCTPoint_New;
    GetIntersectionPlaneAndLine(*sGuNVCTPoint_Current_Ptr,*sGuNVCTPoint_Next_Ptr,sGuNVCTPoint_New,nx,ny,nz,w);
    vector_point_output.push_back(sGuNVCTPoint_New);
   }
  }
  else//текущая точка не видна
  {
   if (next_ret>0)//следующая точка видна
   {
    //добавляем новую точку пересечения
    SGuNVCTPoint sGuNVCTPoint_New;
    GetIntersectionPlaneAndLine(*sGuNVCTPoint_Current_Ptr,*sGuNVCTPoint_Next_Ptr,sGuNVCTPoint_New,nx,ny,nz,w);
    vector_point_output.push_back(sGuNVCTPoint_New);
    //добавляем сдудующую точку
    vector_point_output.push_back(*sGuNVCTPoint_Next_Ptr);
   }
  }
 }
}

И вот только после выполнения такого отсечения у вас, наконец-таки, корректно заработает вывод трёхмерной графики на PSP с помощью GU. Можете создавать игру! :)



Кстати, можно также использовать для скалярного произведения векторов векторный процессор PSP. Например, вот функция, определяющая требуется ли вообще отсечение (выдранная по кусочкам из того же Quake 1 для PSP):

//выполняем отсечение
 vector<SGuNVCTPoint> vector_clip_point;
 //используем векторный процессор PSP
 __asm__ volatile
 (
  "ulv.q	C700, %0\n"	//загружаем вектор в регистр
  "ulv.q	C710, %1\n"	//загружаем вектор в регистр
  "ulv.q	C720, %2\n"	//загружаем вектор в регистр
  "ulv.q	C730, %3\n"	//загружаем вектор в регистр
  :: "m"(FrustumPlane[0]),"m"(FrustumPlane[1]),"m"(FrustumPlane[2]),"m"(FrustumPlane[3])
 );
 //проверим необходимость отсечения
 long vertex=vector_point.size();
 bool clipping=false;
 for(long n=0;n<vertex;n++)
 {
  ScePspFVector4 current_vertex;
  current_vertex.x=vector_point[n].sGuVertex.X;
  current_vertex.y=vector_point[n].sGuVertex.Y;
  current_vertex.z=vector_point[n].sGuVertex.Z;
  current_vertex.w=1;
  float ret1,ret2,ret3,ret4;
  __asm__ volatile
  (
   "ulv.q	C610, %4\n"			// загружаем вектор вершины в регистр
   "vone.s	S613\n"				// ставим единицу в четвёртой компоненте вектора
   "vdot.q	S620, C700, C610\n"	// s620 = вычисляем скалярное произведение
   "vdot.q	S621, C710, C610\n"	// s621 = вычисляем скалярное произведение
   "vdot.q	S622, C720, C610\n"	// s622 = вычисляем скалярное произведение
   "vdot.q	S623, C730, C610\n"	// s623 = вычисляем скалярное произведение
   "mfv	%0, S620\n"			// out1 = s620
   "mfv	%1, S621\n"			// out2 = s621
   "mfv	%2, S622\n"			// out3 = s622
   "mfv	%3, S623\n"			// out4 = s623
   : "=r"(ret1), "=r"(ret2), "=r"(ret3), "=r"(ret4) : "m"(current_vertex)
  );
  if (ret1<0 || ret2<0 || ret3<0 || ret4<0)//требуется отсечение
  {
   clipping=true;
   break;
  }
 }

Тут всё просто — поместили вектора плоскостей и координаты точки в регистры и попросили VFPU выполнить скалярное произведение.

Ссылка на простейшее приложение, выводящее текстуру

Ссылка на движок для PSP с использованием GU

P.S. Я знаю, тут бывают профессионалы по программированию для PSP. Может быть они расскажут, почему GU у PSP так устроен и как правильно работать с ним.
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 19
  • 0
    Когда-то давным давно интересовался программированием на псп, написал какой-то смех, уровня проверки кнопок ПСП-шки. Расскажите, на что сейчас можно рассчитывать при программинге под нее? В плане, на всю мощь консоли, или только софтвар мод? или частично на ускорение? Вы сами тестировали, сколько можно отрендерить полигонов при каких ФПС?
    • +8

      мне кажется, на хабре бы больше заценили, чем тут… не?

      • +2
        uterr
        Это знают только те люди, которые профессионально под неё пишут. Если знать, как работает GU, то можно его полностью использовать. Но руководств по этой части я не видел. Да и SDK у любителей неофициальный. Увы. Но в любом случае, даже с текущим SDK вполне можно использовать ускоритель.
        Нет, я скорость рендеринга не измерял. Там такая штука, что у меня даже все текстуры в видеопамять не помещаются, а в обычной памяти они тормозят. Но игры-то выходили отличные с красивыми текстурами и без тормозов. Значит, есть возможность как-то всё это реализовать.
        А серьёзно, я вот даже не понимаю, почему функции getStaticVramBuffer и getStaticVramTexture работают именно так. И чем определяется размер дисплейного списка. Был бы учебник (на русском :) ) — так вообще никаких проблем бы не было. А так — сплошные догадки.

        san-x
        Может быть. Но PSP уже давно устарела и в целом может считаться старым железом…
        • 0
          Насчет памяти для текстур: PSP умеет S3TC компрессию, а также поддерживает индексированные текстуры с палитрой. Для ускорения работы с текстурами их надо swizzle и, соответственно, передавать GU_TRUE последним параметром sceGuTexMode
          • +1
            А вот что такое этот самый swizzle? Встречал упоминание много раз, но так и не понял, что это.
            • +1
              Переупорядочивание пикселей в памяти для быстрого доступа к соседним пикселям.
              При swizzle организации памяти у вас будет, к примеру, в 1 кешстроке 4 соседних пикселя.
              01
              23

              так как для билинейной фильтрации нужны все 4, то при линейной организации памяти нужно будет считать 2 кешстроки.

              0123
              0123

              А если вы семплируете текстуру под углом, то считывается очень много ненужных данных. Кеши не резиновые.

              Подробнее читайте тут
              fgiesen.wordpress.com/2011/01/17/texture-tiling-and-swizzling

              • 0
                полагаю это:
                en.wikipedia.org/wiki/Z-order_curve
                последний абзац
                • +1
                  beeruser, Myzrael спасибо за информацию! Но тут понимаете какая фигня — я такие статьи смотрел. Почему смотрел, а не читал — я немецкий язык излучал (но не изучил, хоть и старался :) ), а с английским у меня не очень чтобы. Поэтому понять такие статьи мне непросто.
            • +1
              da-nie
              Устарела — да… на хабре всем пофиг — вот уж точно НЕТ… сколько упоротых гиков с упоением читают шедевры о реверс-инжиниринге советских ПЛИС и их старших братьев «оттуда»?? Ну и да, пост о программировании «с хаками» под NES у меня до сих пор где-то в закладках болтается… глупо, но порой перечитываю в приступах ностальгии…
              • 0
                сколько упоротых гиков с упоением читают шедевры о реверс-инжиниринге советских ПЛИС??


                :) Я когда про дисковод для амиги там статью разместил, был комментарий вконтакте в /habr «О, вот и некрофилы подтянулись». :)

                Честно говоря, мне PSP нравится. Хотел к ней тепловизор подключить, но оказалось, что у неё нет USB-HOST. А жаль. Удобно было бы.
                • +2
                  da-nie

                  был комментарий вконтакте в /habr «О, вот и некрофилы подтянулись»
                  ну и отлично… защитная реакция психики на запредельный выброс гормонов уважения и преклонения :)

                  Я вот Амигу не застал, да… но ZX достаточно долго оставался для меня объектом… эммм… насилия, наверное :) даже когда «пацаны уже в PS1 шпилили»…

                  С картриджами от Денди я так и не успел разобраться, т.к. в нашу глубинку почти сразу «набежали» Сеги всякие, PS1, и, как говорится, «всё заверте...»

                  Зато я к тому времени прикрутил к Спектруму самопальный джойстик, к джойстику — температурный датчик DS (даже не спрашивайте, зачем), освоил Спектрумовский обкуренный ассемблер, технику свопа слоев видеопамяти… и с тех пор наши пути «с пацанами» окончательно разошлись…

                  теперь в моем мире есть «денди/сеги/псп/псN/IOS и все, что просто работает», и есть «ZX/ПК/AVR/Root@Android/ и все, что можно просто разобрать/перепрошить/переделать-непонятно-зачем»… причем, далеко не всегда я выбираю второе, но вот «сияет и манит» оно всегда ярче и громче…

                  … нннда… пятница провоцирует на ностальгию…
                  • 0
                    … ну окей, окей, псп теперь перекочевала в кастомайзабл класс…
                    • 0
                      Я вот Амигу не застал, да… но ZX достаточно долго оставался для меня объектом… эммм… насилия, наверное :) даже когда «пацаны уже в PS1 шпилили»…


                      Я с ZX сидел до февраля 2001 года. :) В школе и в институте были, конечно, PC. Но домой купить такое было всё-таки дорого. А вот амигу 500 мне отдали лет 5 назад. В 90-е я её в магазинах не видел (но, говорят, на Юноне в СПб она была). Зато видел на витрине магазина Panasonic 3DO с играми от American Laser Games — вот был фетиш детства.

                      Жаль, Sony для PSP SDK так и не открыла. А могла бы — зачем его скрывать сейчас, всё равно на PSP уже мало кто играет.
                      • 0
                        зачем его скрывать сейчас, всё равно на PSP уже мало кто играет.
                        возможно, чтобы про нее побыстрее забыли, и не отвлекались от покупки Виты?
              • 0
                del
                • 0
                  Вот куда нужно подключить ИИ. А не шашечки всякие..;)
                  • +1
                    А есть ли у Вас опыт с PSP Devkit? Я разжился такой штукой для музея, но еще руки не дошли включить и посмотреть, что там :)
                    image
                    • 0
                      Судя по инету, эта белая коробочка — писалка UMD-дисков. Нет, я с такой не встречался. :)
                    • 0
                      Знакомый юзал PSP 10 лет назад. Уже история.

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.