Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #115, ИЮЛЬ 2008 г.

Карманный бильярд

va1en0k (fromxa@va1en0k.net)

Хакер, номер #115, стр. 115-092-1

Прокачиваем PlayStation: Portable (часть 2)

Вероятно, ты читал первую часть статьи. Там речь шла о том, как выводить текст на экран, а также о простейшей экранной клавиатуре и какой-никакой отладке. Теперь, когда с debug mode (так называется текстовый режим) более-менее покончено, пришло время разобрать основной арсенал двухмерного кодера.

Как пройти в библиотеку?

Среди всего скачанного на твой компьютер месяц назад не было библиотек, которые мы собираемся использовать сегодня, а именно – zlib и libpng. Поэтому самое время запустить Cygwin Shell и начать получать их из репозитария. Устанавливаются библиотеки так:

svn checkout svn://svn.pspdev.org/psp/trunk/zlib

cd zlib

make

make install

cd ..

rm -Rf zlib

svn checkout svn://svn.pspdev.org/psp/trunk/libpng

cd libpng

make

make install

rm -Rf libpng

В результате последовательного запуска этих команд на компьютер будут скачаны исходные коды библиотек (svn checkout), после чего выполнится сборка (make) и установка (make install). В конце, в целях экономии места и чтобы не смущать программиста, директории с исходниками будут удалены (rm -Rf).

Теперь можно переходить к использованию скачанного кода. Открываем наш старый main.cpp (или как ты его назвал?) и, истории ради сохранив его под другим именем, начинаем модифицировать. Первое, что мы напишем – мозаику из кучи логотипов "][акера" (логотип 16 на 16 в формате PNG ты найдешь на диске). Конечно, никто не мешает тебе взять любую другую картинку, преобразовав ее в PNG и поменяв название.

Переименуем нашу программу, изменив первый параметр макроса, задающего информацию о модуле:

PSP_MODULE_INFO("Xakep Logos", 0, 1, 1);

Теперь изменим секцию инклудов, добавим в нее новые библиотечные файлы:

#include <pspdisplay.h>

#include <pspctrl.h>

#include <pspkernel.h>

#include <pspdebug.h>

#include <pspgu.h>

#include <png.h>

#include <stdio.h>

#include "graphics.h"

Как видишь, появились некоторые новые заголовочные файлы, например, graphics.h (описывает некоторые полезные в работе с картинками функции). Возьми его на диске вместе с graphics.c, framebuffer.c и framebuffer.h и положи в папку с основным файлом. В них содержится код, который в противном случае пришлось бы писать самому.

Ну что ж, определим простой макрос:

#define printf pspDebugScreenPrintf

Он всего лишь позволяет обращаться к функции pspDebugScreenPrintf – как будто это функция printf. Как ты помнишь, их формат и назначение абсолютно совпадают, так что макрос повысит читабельность кода и упростит программирование.

«Странные» функции ExitCallback(), CallbackThread() и SetupCallbacks() остаются без изменений, потому что никаких новых каллбэков нам не потребуется, а выход из программы все равно должен происходить. Всю функцию main() придется заменить на новый код, который ты можешь рассмотреть на соответствующей врезке или глянуть на диске (сначала смотрим код и пишем, потом – разбираем).

Ну и наконец, совершим новый Makefile, учитывающий тот факт, что у нас теперь четыре файла вместо двух и новые библиотеки:

TARGET = hello

OBJS = main.o graphics.o framebuffer.o

CFLAGS = -O2 -G0 -Wall

CXXFLAGS = $(CFLAGS) -fno-exceptions -fno-rtti

ASFLAGS = $(CFLAGS)

LIBDIR =

LIBS = -lpspgu -lpng -lz -lm

LDFLAGS =

EXTRA_TARGETS = EBOOT.PBP

PSP_EBOOT_TITLE = Image Example

PSPSDK=$(shell psp-config --pspsdk-path)

include $(PSPSDK)/lib/build.mak

Работа с графикой

Разберем представленный листинг (когда соберешь его командой make и станешь закидывать на приставку, не забудь скопировать туда же картинку xakep.png с диска!). Код почти всех новых функций содержится в graphics.cpp и его можно посмотреть в любой момент, поэтому я ограничусь простым описанием. Структура Image хранит четыре значения помимо самой картинки: ее размеры (Image::imageHeight – высота и Image::imageWidth – ширина) и два значения, описывающие размеры двухмерного массива цветов Color* data, в котором хранится картинка как набор разноцветных точек.

Функция blitAlphaImageToScreen() отображает картинку или ее часть на экране (в виде прямоугольника). Первые два аргумента – координаты выводимого прямоугольника на хранящейся в памяти картинке, еще два параметра – его размеры (если выводим всю картинку, то параметры должны быть 0, 0, ourImage -> imageWidth, ourImage -> imageHeight). Потом идет ссылка на структуру с картинкой (типа Image) и координаты на экране выводимого прямоугольника (мы задаем их как произведение ширины или высоты на номер текущего столбца или строки, которые перечисляются в цикле). Ну, а flipScreen() меняет экран на содержимое видеопамяти, в которую мы запихивали картинки.

Помимо blitAlphaImageToScreen() в graphics.cpp есть еще несколько похожих функций. Например, blitImageToScreen() действует также, но учитывает альфа-канал (еще 8 бит в структуре Color, отвечающие за прозрачность точки). «Сестры» blitAlphaImageToImage() и blitImageToImage() отличаются тем, что копируют картинку или ее кусок не на экран, а в другую структуру типа Image, ссылка на которую передается им последним аргументом.

Вместо вызова loadImage() для получения картинки из файла можно воспользоваться createImage() (два параметра: ширина и высота будущей картинки) – она возвращает указатель на пустую черную картинку заданных размеров. После манипуляций с картинкой ее можно очистить и залить цветом: clearImage() (опять два параметра: цвет и указатель на картинку). Цвет тут задается структурой типа Color, создать которую можно при помощи макроса RGB:

Color red = RGB(255, 0, 0);

Точно также можно очистить и весь экран: clearScreen(RGB(0, 255, 0)). Отдельный прямоугольник на картинке или экране можно залить при помощи функций fillImageRect или fillScreenRect, передав цвет, координаты и размеры прямоугольника, а для fillImageRect – еще и указатель на картинку, последним аргументом. Отдельный пиксель на экране или картинке задается функциями putPixelScreen или putPixelImage; их параметры: цвет, координата по x (по горизонтали, от нуля до 272, если на экране) и по y (по вертикали, на экране может быть от нуля до 480); ну а получить его цвет можно через getPixelScreen и getPixelImage (параметры – координаты x или y, ну и указатель, если функция работает с картинкой). На картинке можно написать и текст: printTextImagе; координаты текста – это два первых параметра, потом идет указатель на строку, цвет и указатель на картинку. Еще пара функций: drawLineScreen и drawLineImage рисуют линии, если передать им координаты точек начала и конца и цвет:

sceDisplayWaitVblankStart();

drawLineScreen(10, 10, 70, 70, RGB(255, 0, 0));

drawLineScreen(10, 70, 70, 10, RGB(0, 255, 0));

flipScreen();

Если написать это вместо всего, что касается картинки в main(), то можно увидеть зеленую и красную линию крест-накрест, как на скрине.

Простые движения

Попробуем теперь заставить этот крест ездить по экрану. Если ты внимательно читал первую статью (хотя бы часть про кнопки), у тебя не будет с этим проблем:

SceCtrlData pad;

int x=0, y=0;

while(1) {

sceDisplayWaitVblankStart();

sceCtrlReadBufferPositive(&pad, 1);

if (pad.Buttons & PSP_CTRL_UP) y -= 10;

if (pad.Buttons & PSP_CTRL_DOWN) y += 10;

if (pad.Buttons & PSP_CTRL_LEFT) x -= 10;

if (pad.Buttons & PSP_CTRL_RIGHT) x += 10;

drawLineScreen(x + 10, y + 10, x + 70, y + 70,

RGB(255, 0, 0));

drawLineScreen(x + 10, y + 70, x + 70, y + 10,

RGB(0, 255, 0));

flipScreen();

clearScreen(RGB(0, 0, 0));

}

Все просто: когда ты нажимаешь на кнопки-стрелки, числа x и y, которые прибавляются к координатам точек, увеличиваются или уменьшаются с шагом 10, после чего выводится крестик.

Гони волну!

В завершение разговоров о двухмерной графике я расскажу, как разогнать PSP – ведь при работе с картинками скорость «решает»! Тактовая частота процессора приставки по умолчанию равна 222 МГц, но ее абсолютно безопасно можно разогнать до предусмотренной создателями частоты в 333 МГц, точно так же как и частоту графического чипа до 166 МГц. Почему такие частоты не стоят по умолчанию? В Sony решили, что 222-ух мегагерц вполне хватит для всего, что в ближайшем будущем придет в голову разработчикам, и в целях увеличения времени работы от батарейки без подзарядки не стали повышать до 333. Сейчас они, видно, уже раскаялись в этом, поскольку в игре God of War: Chains of Olympus частота на время игры официально повышается. Если ты уже перестал сомневаться, то смело добавляй в начало кода такие строки:

scePowerSetClockFrequency(333, 333, 166);

Первый параметр устанавливает частоту основного процессора, второй – Media Engine, а третий – графического чипа (ставь от 1 до 166 МГц). Еще раз повторюсь: если ставить разрешенные частоты, то ничего плохого не стучится – только хорошее.

Звуковые библиотеки

При помощи вышеописанного твоя программа может оказаться на графическом уровне игры Марио (если ты напишешь нормальный 3D-движок, пользуясь только этими функциями – это уже будет фантастикой). Не хватает только звука! К счастью, помогли замечательные разработчики с ps2dev.org (так называется один из центральных сайтов, посвященных разработке под все сониевские игровые консоли). На этом сайте можно найти немало библиотек и отличный форум с кучей советов. Кроме того, там портировали libmad (MPEG audio decoder), которая умеет играть любые запакованные в MPEG файлы, в том числе, mp3. Итак, скачаем исходные коды с svn и установим:

svn checkout svn://svn.ps2dev.org/psp/trunk/libmad

cd libmad

make

Если сейчас попробовать набрать make install, шелл выдаст странное сообщение «не в тему». Это значит, что установку наши предшественники еще не доделали, и придется устанавливать библиотеку самому:

cp -Rf ./include /usr/local/pspdev/psp/

cp -Rf ./lib/libmad.a /usr/local/pspdev/psp/lib

Так мы копируем заголовочные файлы и библиотеку libmad.a в папку с pspdev. Теперь можно удалить скачанные исходники:

cd ..

rm -Rf ./libmad/

Отлично! Пора начинать изменять сами файлы с кодом (разумеется, предварительно надо сделать бэкап). Оставь только несколько заголовочных файлов – их хватит за глаза:

#include <pspkernel.h>

#include <pspctrl.h>

#include <pspdebug.h>

#include <pspdisplay.h>

#include <pspaudio.h>

#include <pspaudiolib.h>

#include <psppower.h>

#include "mp3player.h"

Да, mp3layer.h – еще один файл, который тебе придется поискать на диске (вместе с mp3player.c). Он был взят из программы PSPMediaCenter. После изменения инклудов переименуй программу (первый аргумент в вызове макроса PSP_MODULE_INFO) на «Mp3 Player» и прокрути файл до функции main() (волшебные функции с каллбэками, как обычно, остаются неизменны). Новый код (между вызовами SetupCallbacks() и sceKernelSleepThread()) прост, как никогда:

MP3_Init(1);

MP3_Load("test.mp3");

MP3_Play();

while(MP3_EndOfStream() != 1) sceDisplayWaitVblankStart();

MP3_Stop();

MP3_FreeTune();

Тут мы инициализируем mp3-проигрыватель и играем test.mp3. Разумеется, музон сам собой не появится! Тебе придется переименовать свой любимый трек в «test.mp3» и положить его в папку с прогой (учти, если твоя программа заработает не так, как надо, то есть вероятность, что ее будет невозможно завершить до конца трека). Когда аудиопоток заканчивается, проигрывание останавливается и память освобождается при помощи MP3_FreeTune(). Ну и еще изменится Makefile, в него мы добавим еще парочку библиотек:

TARGET = mp3

OBJS = mp3player.o main.o

CFLAGS = -O2 -G0 -Wall

CXXFLAGS = $(CFLAGS) -fno-exceptions -fno-rtti

ASFLAGS = $(CFLAGS)

LIBDIR =

LIBS = -lmad -lpspaudiolib -lpspaudio -lpsppower

LDFLAGS =

EXTRA_TARGETS = EBOOT.PBP

PSP_EBOOT_TITLE = MP3 Player Example

PSPSDK=$(shell psp-config --pspsdk-path)

include $(PSPSDK)/lib/build.mak

Собираем, заливаем, открываем и наслаждаемся. Если кажется, что звука нет… – может, ты забыл поставить громкость повыше? На экране библиотека (сама!) показывает информацию о файле: например, битрейт или формат. После того, как мы взбодрились музыкой, можно придумать еще чего-нибудь, скажем, выводить время, которое уже прошло от начала трека. В этом поможет функция MP3_GetTimeString() с аргументом-указателем на буфер, в которую будет записано время в формате «часы:минуты:секунды». Еще можно останавливать воспроизведение по нажатию крестика – используется функция MP3_Stop(). Отлично, этого хватит, чтобы легко подключать саундтрек к твоим играм, да и для простейшего проигрывателя достаточно. Теперь можно научиться работать с файлами – например, плейлистами или настройками.

Работа с файлами

Работа с файлами – ничуть менее важное умение для программы, чем вывод на экран и получение состояния кнопок. В начале статьи мы касались работы с изображениями, а сейчас разберем работу с любыми другими. Я покажу, как открывать файл, считывать его в память и закрывать. Программа будет считывать содержимое файла и выводить его на экран. Из заголовочных файлов потребуются только pspkernel.h, pspdebug.h, stdio.h и stdlib.h; Makefile ты можешь взять из первой части статьи.

Последние два заголовочных файла абсолютно стандартны для языка С, и работать с ними ты наверняка умеешь. Файл открывается функцией fopen, первый параметр которой – имя файла, второй – тип доступа (например, «rb» – чтение бинарного файла, а «w» – запись в обычный). При помощи fseek(pFile, 0, SEEK_END) указатель можно прокрутить до конца; это нужно, чтобы быстро определить его размер через ftell(pFile). Перематываем назад: rewind (pFile) и, наконец, считываем его содержимое в буфер: fread (buffer,1,lSize,pFile). Каждый раз надо проверять указатели на NULL: на файл и на буфер. В случае неудачи можно вывести сообщение об ошибке или просто прекратить выполнение программы (разумеется, в серьезном продукте надо выводить сообщение) при помощи простейшей sceKernelExitGame() – нечто вроде exit().

Итак, мы разобрали картинки, музыку и файлы. Этого вполне хватит для написания чего угодно: от читалки книг до плеера, от морского боя до какой-нибудь большой и сложной игры, с сохранением настроек и «рекордов». Возможно, в будущем мы еще поговорим о работе с сетью и трехмерной графике. Удачи!

CD

На компакт-диске лежат исходные коды программ, написанных мной для этой статьи.

WWW

Интересную информацию и полезные форумы (на английском языке) можно найти на сайтах http://ps2dev.org/psp/ и http://www.psp-programming.com/.

INFO

Для PSP доступно немало других OpenSource библиотек. Попробуй использовать, если хочешь.

Основная функция программы

int main() {

char buffer[200];

// буфер, в нем хранится картинка как набор байтов

Image* ourImage;

// указатель на структуру, в которой хранится

картинка и информация о ее размерах

pspDebugScreenInit();

SetupCallbacks();

initGraphics();

// инициализирует "графический" режим

sprintf(buffer, "xakep.png");

// считываем картинку из файла в буфер

ourImage = loadImage(buffer);

// преобразуем буфер в структуру типа Image

if (!ourImage) {

printf("Fail!\n"); // не получилось

} else {

int max_x = 480/ourImage->imageHeight;

// максимальное количество картинок в столбце

(количество строк)

int max_y = 272/ourImage->imageWidth;

// максимальное количество картинок в строке

(количество столбцов)

sceDisplayWaitVblankStart();

for (int x = 0; x < max_x; x++) // по строкам

for (int y = 0; y < max_y; y++) // по столбцам

blitAlphaImageToScreen(0, 0, ourImage->

imageWidth, ourImage->imageHeight, ourImage,

x*ourImage->imageHeight, y*ourImage

->imageWidth);

flipScreen();

}

sceKernelSleepThread();

return 0;

}

Содержание