Вот полные исходники моего софтверного 2.5D движка для PSP. Это тот же самый движок, что и тут (это моя статья). Просто я его перенёс на PSP и решил его выложить, пока PSP окончательно не стала никому не нужна. К сожалению, работает не быстро. Но работает.
Да вот, вспомнил про PSP и перенёс движок. А то он у меня и для Windows и для QNX, и для MS-DOS есть, а для PSP-нет. Придумать бы, как его ускорить - впрочем, можно разрядность цвета понизить. И ещё при перемещениях очень хорошо видны смазывания цветов. Интересно, неужели экран такой инерционный? Вроде бы в играх я такого не замечал (хотя в DOOM вроде бы я такое уже видел).
Другие консоли: Все PSP, все PSV, SCPH-1002, SCPH-102, SCPH-77008, CECH-4208C, SCPH-1000R
Регистрация: 19.03.2008
Адрес: Россия
Сообщений: 5,731
Вы сказали Спасибо: 818
Поблагодарили 3,834 раз(а) в 2,013 сообщениях
Сила репутации: 1
Репутация: 3857 
(репутация неоспорима)
Ilsor,
многие тяжёлые игры работают в 16 битах (мало VRAM).
Что меня очень печалит, когда я смотрю на чёрный цвет.
И экран действительно достаточно инерционный (Pong).
Это забавно, но я сегодня был на Юноне (это в СПб). Много лет я там бывал, но вот именно сегодня попались и Спектрум в красивом оформлении за 1500 и PSP Street.за 1000. А первой я увидел PSP и её и купил из интереса. Будет у меня две PSP. А спектрум уже не купил - мне на вокзал нужно было встречать и лишний груз ни к чему. А жаль. Вот такой облом. А раньше ничего такого не попадалось много лет. Ну, кроме МК-85.
Жаль, прошивка только виртуальная бывает к ней... И экран ооооочень инерционный. Но работает. Будет запасной.
А вот в официальных играх шлейфов-то почти и нет. Надо подумать, откуда они берутся при прямой отрисовке без ускорителя.
Последний раз редактировалось Ilsor; 18.06.2017 в 20:49.
Так-то оно так, но приятно, когда через 30 лет включил и работает (будем считать, что флэшка не сотрётся ), а не вспоминать, что там надо было сделать, чтобы всё заработало.
Понял, откуда шлейф. Вот из-за этой функции: sceDisplaySetFrameBuf((void*)VideoBuffer,512,PSP_DISPLAY_PIXEL_FORMAT_8888,0); Последний параметр у меня 1 был - синхронизация:
PSP_DISPLAY_SETBUF_IMMEDIATE Buffer change effective immediately. - это 0
PSP_DISPLAY_SETBUF_NEXTFRAME Buffer change effective next frame. - это 1
Честно говоря, я не заметил, что там такие значения и заданы через enum. Подумал, что либо есть, либо нет синхронизации. Вот как полезно бывает читать документацию. Но в архиве я не заменил на PSP_DISPLAY_SETBUF_IMMEDIATE - там остался 0. У себя-то я уже исправил на PSP_DISPLAY_SETBUF_IMMEDIATE вместо нуля, но не перезалил.
Он и синхронизирует. Я ещё кое-что изменил в движке - в физической части. Сейчас FPS стабильный при движении. Но, правда, только 20-30 FPS выдаёт в полноэкранном режиме 480x272. Но на 320x240 около 30-40. Архив в первом сообщении перезалит.
Попытался задействовать графический ускоритель PSP для вывода картинки движка. Занятно. Едва координаты точки треугольника оказываются за спиной (координата Z больше либо равна нулю), как ускоритель просто выбрасывает весь выводимый треугольник! Это что ещё за шутки такие? Никогда тот же OpenGL так не делал - это задача ускорителя перенести точки на переднюю плоскость отсечения. И он это делает даже на PSP когда координата Z отрицательна! Открыл исходники Quake 1 для PSP. Вижу там целый модуль clipping.h - не знаю, из оригинального он Quake или нет, но похоже он как раз и занимается отрезанием частей полигонов, находящихся за спиной. Я правильно понимаю, что GU не умеет работать с положительными Z координатами точек?
И ещё я нифига не понял, что это такое: sceGumUpdateMatrix(); - я наивно полагал, что матрица задействуется сразу же после её задания. И оно так и было, пока я не начал менять матрицы каждый кадр в цикле (менял плоскость отсечения). Что за бред?
С текстурами такая же фигня - без sceKernelDcacheWritebackAll(); на текстурах артефакты - полоски и точки. Что за странный GU у PSP? Ему постоянно нужно сбрасывать кэш при смене текстур и просить пересчитать матрицы? Зачем же всё это нужно?
Последний раз редактировалось Ilsor; 23.06.2017 в 20:41.
Сделал отсечение по Z. А вот и не помогло. У GU проблема вовсе не в Z. У него какая-то проблема с проецированием - если точки куда-то попадают про проецировании, то они выбрасываются вместе с выводимым полигоном. Но вот какое это условие - я не знаю. Можно, правда, из матрицы проецирования найти все ограничивающие вид плоскости и отсечь по ним. Тогда, наверное, заработает.
Всё, заработало. Разобрался с тем, что делают в Quake-1. Фишка вот какая - GU нужно чтобы вершины после проекции попадали в видимое окно. Иными словами, нужно отсечь вершины полигонов по плоскостям, ограничивающим вид игрока слева, справа, снизу и сверху. А передняя плоскость отсечётся автоматом.
Как это сделать? Считать три матрицы - 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.
Вычисляется это всё так:
Функции для перевода вектора в нормализованное представление (x,y,z,w) и умножения матрицы на матрицу:
Теперь у нас в массиве векторов frustum заданы плоскости отсечения.
А дальше надо просто найти пересечения сторон полигона с плоскостями и посчитать новые точки, а старые (которые вне плоскостей) выкинуть. Это делается так:
//----------------------------------------------------------------------------------------------------
//получить точку пересечения прямой и плоскости
//----------------------------------------------------------------------------------------------------
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 использовать для скалярного умножения векторов:
//выполняем отсечение
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" // Now set the 4th entry to be 1 as that is just random
"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;
}
}
Но эта функция из движка, а не из приводимой ниже программы. Взял я этот код процессора из того же Quake-1.
Движок под GU тоже заработал. Только нифига ускорения не вышло - тормоза одни. Причём, при отключённом текстурировании скорость резко возрастает. Может, нужно как-то текстуру в видеопамять перебросить.
Вот сама демонстрационная программа (не забудьте скопировать текстуру в каталог с eboot.pbp):
Кстати, если текстуры поместить в видеопамяти (которая получается по sceGeEdramGetAddr()), то скорость текстурирования резко возрастает. Вот только на все текстуры памяти не хватает. Придётся в видеопамяти кэшировать только те текстуры, которые в данный момент используются при выводе графики.
Вот ссылка на движок под GU с форматом цвета 565 - 16 бит. Этому движку нужны файлы из движка выше - текстуры и карта. Кстати, в той карте есть ошибочно нарисованный сегмент (он наложился поверх другого) в месте, где водопады. Из-за этого там будут глюки с текстурой - будет видно наложение.