配置好开发环境之后,下一步工作当然就是编写著名的Hello World了。鉴于网上关于PSP的Hello World的中英文资料都比较多,再加上我年初实习那阵写的代码和当时配置好的Makefile也找不到了,所以这一节的代码就主要参考了这篇wiki[1]和论坛上的一些资料。

PSP编程最开始接触到的知识点就是PSP的线程模型和回调机制,这是构成PSP自制程序的基本框架。目前还没见到关于这方面内容的详细研究和探索,只是按照sample的代码框架来这么写,顶多查查文档看看每个函数的作用,但无法深入理解内部的机制。所以暂时就不细究了,把程序正常运行起来就大功告成。

另外,在开始写代码之前,请不要忘记设置Visual Studio里Project Properties中的Include Directories的路径,否则虽然可以正常编译链接,但是满屏幕的红色波浪线看着也不爽是吧。

main.cpp

首先包含必要的头文件,pspkernel.h主要定义了线程相关的一些函数,可以理解成windows.h的作用,而pspdebug.h则定义了屏幕输出相关的函数。这两个文件在以后的代码中也都是必备的:

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

还有两个重要的macro需要同时定义:

PSP_MODULE_INFO("HELLO_WORLD", 0, 1, 1);
PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER);

PSP_MODULE_INFO定义了本module的基本信息,是必不可少的一个定义,否则会出现编译错误。第一个参数是本module的名字,可以自己起名。第二个参数是本module的运行模式,0表示user mode,0x800表示VSH mode,0x1000表示kernel mode。第三个参数和第四个参数分别表示本module的版本号的major值和minor值。

PSP_MAIN_THREAD_ATTR定义了执行main函数的线程的属性,THREAD_ATTR_USER指定其是用户线程。

接下来就是编写必要的callback函数,以便让程序能够正常地执行和退出。目前我们需要设置一个全局变量来指示程序是否需要退出,以后可以尝试一下使用Event来代替标记变量。

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

这个callback函数将在用户按下Home键并确认退出之后得到执行(设置代码在下文中)。它将end_flag标记为true,以便终止main函数中的主循环。

然后我们准备建立一个新的线程。先编写线程函数,函数原型是int Func(SceSize args, void *argp)

int thread_func(SceSize args, void *argp)
{
    int cbid = sceKernelCreateCallback("exit callback", exit_callback, NULL);
    sceKernelRegisterExitCallback(cbid);

    sceKernelSleepThreadCB();

    return 0;
}

在这个线程中,首先使用了sceKernelCreateCallback和sceKernelRegisterExitCallback两个函数创建回调函数并将其注册为针对Home键的回调函数(其实应该先判断下cbid的值,cbid<0的话说明创建时出现了错误)。然后sceKernelSleepThreadCB函数让本线程Sleep,但并不是睡死,函数名中的CB表示了“当有callback被调用的时候则醒来”[2]。所以,当exit_callback执行后,thread_func就会退出(但是先后顺序未知,不知道exit_callback函数是否在同一个线程中得到执行)。 最后写main函数:

int main(int argc, char *argv[])
{
    int thid = sceKernelCreateThread("update_thread", thread_func, 0x11, 0xFA0, 0, 0);
    if (thid >= 0)
        sceKernelStartThread(thid, 0, 0);

    pspDebugScreenInit();

    while (!end_flag)
    {
        pspDebugScreenSetXY(0, 0);

        pspDebugScreenPrintf("Hello World\n");
    }

    sceKernelExitGame();

    return 0;
}

sceKernelCreateThread函数用来创建新线程,第三个参数表示线程优先级(值越小优先级越高),第四个参数表示初始stack的大小,第五个参数表示线程属性(一般设为0,其它特殊属性请参考名为PspThreadAttributes的枚举值,在pspthreadman.h文件中定义),第六个参数表示一些额外的选项(请参考SceKernelThreadOptParam结构体)。

这里主要是为了练习一下线程的基本用法,虽说这个update_thread只是注册了个回调函数并等待其执行而已没干别的事。在main中直接设置exit callback按理说也没问题,不过我就懒得尝试了。

处理完线程后就开始准备往屏幕上刷东西了。先用pspDebugScreenInit函数初始化一下屏幕(不初始化会怎样?没尝试),然后进入主循环,在屏幕的(0,0)位置处使用pspDebugScreenPrintf函数不断打印出”Hello World”的文字。其实,关于屏幕打印可研究的地方和细节还有很多,以后再叙。

当用户按下Home键并决定退出后,end_flag被设为1,主循环退出,调用sceKernelExitGame函数结束整个程序。需要注意的是当sceKernelExitGame被调用后,程序立即结束,所以请在之前做好清理工作,并且尽量不要在exit callback中直接调用它[2]。

关于各种线程函数的信息,请参考本地文档或在线文档[3]。

Makefile
TARGET = cotaku
OBJS = main.o
CFLAGS = -O2 -G0 -Wall
CXXFLAGS = $(CFLAGS) -fno-exceptions -fno-rtti
ASFLAGS = $(CFLAGS)
EXTRA_TARGETS = EBOOT.PBP
PSP_EBOOT_TITLE = PSP for cotaku
PSPSDK=$(shell psp-config --pspsdk-path)
include $(PSPSDK)/lib/build.mak

Makefile使用模板即可,TARGET可无视,OBJS写上你所有的源文件(.c或.cpp)对应的.o文件,PSP_EBOOT_TITLE是程序在vsh中显示出来的名字,如图所示:

Build

使用上一章中配置好的Windows开发环境,直接在Visual Studio中按下F7就可以调用psp-gcc(或psp-g++)生成EBOOT.PBP了。将其拷贝到PSP目录下的\PSP\GAME文件夹下的一个新建的文件夹中(不要直接放到GAME文件夹下),即可运行了!

程序运行截图

===========cotaku的分割线===========

坑了好久之后终于将Hello World写出来了……下一篇是写屏幕打印还是写基本图元的显示呢……

参考资料:
[1] PSP自作ソフトプログラミング/開発wiki – helloworld
[2] Basic Callback Thread
[3] Thread Manager Library

» 转载请注明来源及链接:未来代码研究所

Related Posts:

2 Responses to “PSP图形编程研究手记2:Hello World与Makefile”

  • Shiina Luce says:

    VS08用户伤不起,红线在哪里……

  • 暗影吉他手 says:

    @Shiina Luce
    如果你没设置好include的话代码中的宏定义和函数定义VS找不到就会显示红色波浪线了……否则你按F12就能跳转到对应头文件的定义。我之前也是VS08啊。。。难道红色波浪线是Visual Assist起的作用么。。。

Leave a Reply

World Line
Time Machine
Friendly Links
Online Tools