| M | D | M | D | F | S | S |
|---|---|---|---|---|---|---|
| « Feb | Jan » | |||||
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
| 29 | 30 | 31 | ||||
- Allgemein (6)
- Einfache 3D Welten (3)
- Erste Schritte (3)
- GU (4)
- Komplexe 3D Welten (1)
- 26.1.2011: Der Blog zieht um
- 23.3.2010: Der Anstrich - aber mit Plan
- 4.3.2010: 3D Landschaft - optimiert
- 17.2.2010: Landschaft 3D mit GU
- 28.1.2010: Ein Dreieck - GU Test
- 20.1.2010: Homebrew - 3D
- 12.1.2010: GU - und nu ?
- 11.1.2010: Neustart...
- 7.1.2010: Bildschirm aufräumen
- 5.1.2010: Schwarz/Weiß war gestern
PSP Allgemein
PSP Entwicklung
Der Anstrich - aber mit Plan
Die Drahtgitterlandschaften sind fertig gestellt - egal mit welcher Methode - und nun geht es daran auch dieser wieder etwas Farbe einzuhauchen. Dazu bedient man sich sogenannter Texturen. Das sind Bilder einer beliebigen Oberfläche die auf das virtuelle 3D Objekt gelegt werden. Dadurch erscheinen sie realer und plastischer, als wenn Sie einfach nur mit einer einfachen Farbe angestrichen wurden.
Texturen verwalten
Immer wieder , wenn wir 3D szenen auf der PSP darstellen wollen, werden wir die verschiedensten Texturen zum Einsatz bringen. Da macht es Sinn sich darüber Gedanken zu machen, wie wir diese am besten in unserer Homebrew verwalten. Die GU der PSP bringt hier “leider” keine Verwaltungsfunktionen mit. Darum werden wir hier eine sehr einfache Variante in Form einer Texterverwaltungsklasse selber erstellen. Ein paar Grundüberlegungen:
1. die Texturen sollen zunächst nur von PNG Bilddateien gelesen werden
2. ein und das selbe Bild soll als Textur nur einmal geladen werden
3. geladene Texturen sollen individuell wieder freigegeben werden können um Speicher zu sparen
4. die Texturen sollen ge-swizzled werden
5. die Klasse soll einfach erweiterbar sein, zBsp. um Laderoutinen für JPG oder andere Bildformate
Dies sind ein paar gute Rahmenbedingungen. Machen wir uns nun ans Werk. Wir erstellen eine neue Klasse mit dem Namen ClTextureMgr. Im Header definieren wir zunächst ein paar Beschränkungen, wie die maximale Größe einer Textur, die sich auf 512×512 Bildpunkte beschränkt (mehr kann die GU nicht verarbeiten). Zusätzlich eine Struktur, in der alle wichtigen Daten einer Textur die wir verwalten wollen abgelegt sind.
/*
* TextureMgr.h
*/
#ifndef TEXTUREMGR_H_
#define TEXTUREMGR_H_
// Maximale größe einer Textur und anzahl Texturen die verwaltet werden sollen
#define TEX_MAX_WIDTH (512)
#define TEX_MAX_HEIGHT (512)
#define MAX_TEX_COUNT 100
/*
* Definition einer Struktur die alle daten einer Textur enthält
* die hier verwaltet werden soll
*/
typedef struct Texture{
unsigned int id; //ID der Textur
char* name; //Name der Textur/Bilddatei
int type; //Typ der Textur - siehe GU_PSM* Enums
int width, height; //Breite und Höhe der Textur als Potenz von 2
int stride; //Pufferbreite für Zugriff auf Texturdaten als Potenz von 2
bool swizzled; //WAHR wenn Texturdaten ge-swizzled sind
void* data; //Zeiger auf die Bilddaten der Textur
}Texture;
Nun folgt die eigentliche Definition der Klasse. Sie wird als Singleton ausgeprägt und kann somit nicht direkt instanziiert werden. Dies vereinfacht die Verwaltungsaufgaben der Klasse. Es müssen also 2 statische Methoden zum holen und zum abräumen der Singleton-Instanz erzeugt werden. Alle weiteren Methoden werden dann Instanz basiert ausgeprägt. Die Definition sieht dann folgendermaßen aus.
class ClTextureMgr {
public:
/*
* Statische Methoden für den Singleton Zugriff
*/
static ClTextureMgr* getInstance();
static void freeInstance();
/*
* Laden einer Textur aus einer PNG Datei
*/
Texture* loadFromPNG(const char* filename);
/*
* Liefert die verwaltete Textur anhand der ID
*/
Texture* getTexture(unsigned int id);
/*
* Freigeben der Ressorcen für eine Textur
*/
void freeTexture(unsigned int id);
protected:
/*
* Texturdaten "swizzlen"
*/
void swizzle(unsigned char* out, unsigned char* in, unsigned int width, unsigned int height);
/*
* Konstruktor ist protected um direkte Instanziierung zu verhindern
*/
ClTextureMgr();
virtual ~ClTextureMgr();
/*
* es folgen die Member-Variablen zur Klasse
*/
private:
static ClTextureMgr* _instance; //die singleton Instanz
int managedTextures; //Anzahl verwalteter Texturen
Texture* textures; //Liste aller verwalteten Texturen
};
#endif /* TEXTUREMGR_H_ */
Kommen wir nun zur Implementierung der Klasse. Den Start bildet hier eine kleine - von der Klasse losgelöste - Hilfsroutine um von einer gegebenen Zahl die nächst höhere Zahl als Potenz von 2 zu bestimmen (das haben wir schonmal gesehen
). Daneben sind die statischen Methoden für die Verwendung der Klasse als Singleton implementiert.
/*
* TextureMgr.cpp
*/
//die notwendigen Includes
extern "C"{
#include <malloc.h>
#include <stdlib.h>
#include <png.h>
#include <psptypes.h>
#include <pspgu.h>
}
#include "TextureMgr.h"
/*
* eine einfache Hilfsfunktion. Lokal und statisch...
*/
static int getNextPower2(int width)
{
int b = width;
int n;
for (n = 0; b != 0; n++) b >>= 1;
b = 1 << n;
if (b == 2 * width) b >>= 1;
return b;
}
//Am Anfang ist die Singleton instanz nicht erzeugt
ClTextureMgr* ClTextureMgr::_instance = 0;
ClTextureMgr::ClTextureMgr() {
//der Konstruktor initialisiert die Datan
this->managedTextures = 0;
textures = (Texture*)malloc(MAX_TEX_COUNT*sizeof(Texture));
}
ClTextureMgr *ClTextureMgr::getInstance()
{
if (!_instance) {
_instance = new ClTextureMgr();
}
return _instance;
}
void ClTextureMgr::freeInstance(){
if (_instance){
delete(_instance);
}
}
Der nächste Schritt ist die Methode zum laden eines PNG Files als verwaltete Textur. Da bereits an vielen anderen Stellen das laden der PNG Datei beschrieben und auch als Code dargestellt wurde, und weil der original Code Teil des PSPSDK ist, werde ich diesen hier aussparen, wohl aber den für den Texturverwaltungsteil wichtigen Part zeigen.
Texture *ClTextureMgr::loadFromPNG(const char *filename){
//Befor wir das File laden, prüfen ob es schon eine Textur
//mit dem selben Namen gibt, wenn ja eben diese Textur zurück liefern
for (short t=0;t<managedTextures;t++){
if (textures[t].name){
if (strncmp(filename, textures[t].name, strlen(filename))==0){
return &textures[t];
}
}
}
//es ist wirklich eine neue Textur, also diese Laden und die Daten füllen
managedTextures++;
if (managedTextures >= MAX_TEX_COUNT) return NULL;
// get the pointer of the new texture data structure
Texture* newTex = &textures[managedTextures-1];
newTex->name = (char*)malloc(strlen(filename));
strcpy(newTex->name, filename);
/*********************************************************
* hier erfolgt nun das Laden des PNG Files und das Ablegen der Daten im
* Speicher für diese Textur, sollte hier ein Fehler passieren wird NULL zurück-
* gegeben.
*********************************************************/
//vorbereiten für die Rückgabe der Textur
newTex->id = managedTextures;
newTex->swizzled = false;
//veruche die Texturdaten zu swizzlen
//Speicher für die neuen Daten besorgen
void* swizzleData = malloc(sizeof(u32)*newTex->stride*newTex->height);
if(swizzleData){
swizzle((unsigned char*)swizzleData, (unsigned char*)newTex->data, newTex->stride*sizeof(u32), newTex->height);
newTex->swizzled = true;
//Speicher der originalen Daten freigeben
free(newTex->data);
newTex->data = swizzleData;
} else
newTex->swizzled = false;
return newTex;
}
Swizzle
Das nun schon oft erwähnte Wort bedeutet direkt übersetzt: Bauernfängerei
bzw. mit dem Zusatz “Stick” bedeutet es Rührstab. Letzteres trifft hier eher zu. Die Daten der Textur sind im Normalfall ja in einem Datenpuffer ordentlich in Reihenfolge der Bildpunkte abgelegt. Des Swizzle-Verfahren rührt diese Bildpunkte so “durcheinander” dass die Daten nicht mehr so abgelegt sind wie sie auf dem Bildschirm zu sehen sind, sondern optimal von der GU zugegriffen werden können. Das ganze bringt einen großen Geschwindigkeitsgewinn beim Arbeiten mit Texturen.
Eine leicht lesbare - nicht auf Performance getrimmte - Swizzle-Routine wird in unserer Textur-Verwaltungsklasse verwendet:
void ClTextureMgr::swizzle(unsigned char* out, unsigned char* in, unsigned int width, unsigned int height)
{
unsigned int i,j;
unsigned int rowblocks = (width / 16);
for (j = 0; j < height; ++j)
{
for (i = 0; i < width; ++i)
{
unsigned int blockx = i / 16;
unsigned int blocky = j / 8;
unsigned int x = (i - blockx*16);
unsigned int y = (j - blocky*8);
unsigned int block_index = blockx + ((blocky) * rowblocks);
unsigned int block_address = block_index * 16 * 8;
out[block_address + x + y * 16] = in[i+j*width];
}
}
}
Abschließend und der Vollständigkeit halber die Methode um eine verwaltete Textur einer bestimmten ID zurückzuliefern, eine Textur freizugeben und den Klassendestruktor.
Texture *ClTextureMgr::getTexture(unsigned int id){
//Prüfen das die ID auch valide ist
if (id > managedTextures){
return NULL;
}
return &textures[id-1];
}
void ClTextureMgr::freeTexture(unsigned int id){
Texture* text = &textures[id-1];
if (text){
free(text->data);
free(text->name);
text->data = 0;
text->name = 0;
}
}
ClTextureMgr::~ClTextureMgr() {
//Allen reservierten Speicher freigeben
for (int t=0;t<managedTextures;t++){
if (textures[t].data) free(textures[t].data);
if (textures[t].name) free(textures[t].name);
}
if (managedTextures)
free(textures);
}
Antwort schreiben
Sie müssen als angemeldet sein, um einen Kommentar schreiben zu können.