Pso Dev Wiki Staging Thread

For BB characters it's typically the other way around (not sure if it's always the case). So I thought my solution was safer, this way it doesn't matter where what is. Sega likes to throw a curve ball every now and then.
 
For now I've created a "dreamcast" namespace in the wiki to document the PSO v2 formats as it. My thinking is that the Dreamcast documentation can serve as a base for file formats in Gamecube and PsoBB that haven't been documented yet.

Pages added:
AFS Archive
BML Archive
GSL Archive
Ninja Model
Ninja Motion

Pvr, and pvm files seems like the next step in terms of file formats to start documenting.
 
I was hoping to add some more information over the weekend, but either way it's time for some more forum spam. For document I wrote, there was generally already something there so I was able to contrast with what else needed to be added. So for pvmh and pvrt, I guess I can at least add the basic bullet points and then if needed fill in more context.

So Dreamcast uses a texture format called .pvr, which is a texture format for "Power VR" which is the company that made the graphics chip, which was licensed by NEC. The archive format for .pvr is .pvm, so I'll start with the archive format first.

.Pvm files have the magic number of "PVMH"
The magic number is followed by the length of the header

The first two short values in the header of .pvm are the number of textures in the file and the flags that define the format used to describe the pvr entries listed in the header.

Each pvr entry in the header has an id which is an unsigned short. Following that in order the attributes appear.

If bit 3 is set in the flags, then the pvr entry will contain the texture name (length of 0x1c).
If bit 2 is set in the flags, then the pvr entry will contain the encoding format (twiddled/vq, rectangle) as a short (length of 2)
If bit 1 is set in the flags, then the pvr entry will contain the height and width as a short (length of 2, values need to be masked out)
If bit 0 is set in the flags, then the pvr entry will contain the global id which is a unique uint32_t for that texture (length 4)

As far as I know, pso only every uses bit 3, I don't think i've come across the encoding, width and height, or global id. From there each pvr image image in the file will be listed after the header. Pvr images start with the magic number PVRT, and are generally stacked as soon as the previous one finished, but I have found a couple of examples where that doesn't work. So the best option seems to be to seek to the next instance of 'PVRT' to read all of the textures in the archive.

Also each PVR file included in the PVM archive is the same format as would be found in a stand alone PVR file.

Example Implementation:

Code:
    def readHeader(self):
        #Create array
        texList = []

        #Check for PVMH file header
        iff = self.bs.readUInt()
        if iff != PvmArchive.PVMH:
            return 0

        #Offset of first texture entry
        texOfs = self.bs.readUInt()
        self.bs.setOffset()

        #Read flags and number of textures
        flags = self.bs.readUShort()
        nbTex = self.bs.readUShort()

        for i in range(nbTex):
            tex = { 'id' : self.bs.readUShort() }

            if flags & BIT_3:
                #Filename bit flag
                tex['name'] = self.bs.readStr(0x1C)
            if flags & BIT_2:
                #File format bitflag
                tex['format'] = self.bs.readUShort()
            if flags & BIT_1:
                #File dimensions bit flag
                tex['size'] = self.bs.readUShort()
            if flags & BIT_0:
                #Global Index bit flag
                tex['index'] = self.bs.readUInt()

            texList.append(tex)

        self.bs.seek_set(texOfs)
        return texList

From: https://github.com/seiche/Shenmue-Export-Tools/tree/master/PythonPVR
 
Okay, for PVR textures I guess it makes the most sense to go from top to bottom. Like other files PVR textures use a magic number "PVRT" followed by the length of the pvr texture.

The header of pvr is 8 bytes in length:
Code:
struct pvr_header_t {
    uint8_t encode_format;
    uint8_t pixel_format;
    uint8_t nop[2];
    uint16_t width;
    uint16_t height;
}

So the full list of encoding values for the encode format byte is:
Code:
TWIDDLED           = 0x01
TWIDDLED_MM        = 0x02
VQ                 = 0x03
VQ_MM              = 0x04
PALETTIZE4         = 0x05
PALETTIZE4_MM      = 0x06
PALETTIZE8         = 0x07
PALETTIZE8_MM      = 0x08
RECTANGLE          = 0x09
STRIDE             = 0x0B
TWIDDLED_RECTANGLE = 0x0D
ABGR               = 0x0E
ABGR_MM            = 0x0F
SMALLVQ            = 0x10
SMALLVQ_MM         = 0x11
TWIDDLED_MM_ALIAS  = 0x12
Out of this list, I don't think I've come across Stride or ABGR. For individual textures, the list breaks down into twiddled, vq and rectangle. And Palette 4 and Palette 8 formats are used with an external palette file .pvp.

The full list of pixel types is:
Code:
ARGB_1555          = 0x00
RGB_565            = 0x01
ARGB_4444          = 0x02
YUV_422            = 0x03
BUMP               = 0x04
RGB_555            = 0x05
YUV_420            = 0x06

Though the only formats pso uses is ARGB_1555, RGB_565, or ARGB_4444. So I don't know about the other formats. But so far from what I've seen from other Dreamcast libraries and games, those seem to only use or support these three formats.

Encoded Data

After the header is the encoded image. The easiest one is rectangle, which will just have the 16 bit colored data in order.

Twiddled

The second easiest is "twiddled", and the easiest way to describe twiddled is with an image from the Katana SDK.

twiddled.PNG

Basically twiddled is a function that subdivides the width and height of the image and recursively calls itself until it ends up with a size of 1. The resulting order of which is shown above, where it produces these blocks of four pixels that cascade over the image. I think the reasoning is that this allows for the graphics card to split up texel's to be rendered to a face faster, or something like that.

Twiddled Mipmap

Twiddled textures can include mipmaps, which will divide the size of the texture in half until there is only one pixel. The mipmap format will start with one pixel, and then continue to double in width and height until a texture has reached the maximum width and height defined in the header. So to skip past these to read the full size image, you will need to calculate the size of the mipmap and then seek to the full size image.

VQ (Vector quantization)

Is a fancy way of saying 'a texture with a palette'. Though the way the Dreamcast handled VQ is pretty weird. VQ encoded images have 4 flavors, Small VQ, Small VQ Mipmap, VQ and VQ Mipmap.

The general way that all of these work is the same. VQ images declare a codebook or palette. Each codebook entry has 4 color values which represent a square block of 2x2 pixels. The size of the codebook is 256 for standard VQ. For small vq, the size of the codebook depends on the dimensions of the image, and if there are mipmaps or not.

If there are mipmaps, then for width is 8 or 16 the codebooksize if 16.
If there are mipmaps, then for width is 32 the codebooksize if 64.

If there are no mipmaps, then for width is 8 or 16 the codebooksize if 16.
If there are no mipmaps, then for width is 32 the codebooksize if 32.
If there are no mipmaps, then for width is 64 the codebooksize if 128.

The format of VQ is that 1 byte now represents an entry in the codebook, which is 2x2 pixels. So that means that the image data portion of the data is the width * height divided by 4. So if mipmaps exists, you will need to calculate the encoded image size for each mipmap and then seek to the largest encoded image size defined in the header.

For the actual encoded image data, the encoded image data (the bytes that reference the code book entries), are also twiddled. So these need to be detwiddled, and once they get a block size of 1, that is used to reference a 2x2 pixel area in the image, referenced by the codebook.

Palettize 4/8

Palletize uses an external .pvp palette file. The format of the file is the magic number is "PVPL" followed by the size of the palette file. The header is:

Code:
struct pvp_header_t {
    uint16_t pixel_format;
    uint16_t nop[2];
    uint16_t count;
}

So it's basically a list of 16 bit rgb values.

When a pvr texture uses the Palettize 4 or palettize 8 format, 4 or 8 is the number of bits per pixel. So Palettize 4 each byte is two pixels or a palette size of 16 possible values and Palettize 8 each byte is one pixels or a palette size of 256 possible values. And like everything else, the order of the bytes is twiddled. And like the other values, if mipmaps are present you will need to seek to the largest image size.

Source

The code I used while writing my python library was Dreamshell: https://github.com/DC-SWAT/DreamShe...291d60256cd75b74d68cc/lib/SDL_image/IMG_pvr.c
 
Last edited:
Great work again! Kind of want to try adding some textures to the model viewer and quest editor on phantasmal.world now.

The resulting order of which is shown above, where it produces these blocks of four pixels that cascade over the image. I think the reasoning is that this allows for the graphics card to split up texel's to be rendered to a face faster, or something like that.

This is usually done to improve memory locality. Pixels close to each other will also be close in-memory this way. I think GPUs also store textures in this order or Z-order. See https://en.wikipedia.org/wiki/Z-order_curve#Texture_mapping for more info.
 
This is usually done to improve memory locality. Pixels close to each other will also be close in-memory this way. I think GPUs also store textures in this order or Z-order. See https://en.wikipedia.org/wiki/Z-order_curve#Texture_mapping for more info.

Oh nice, the wiki has a much better image than the Katana SDK docs.

1920px-Moser–de_Bruijn_addition.svg.png


Wikipedia said:
Some GPUs store texture maps in Z-order to increase spatial locality of reference during texture mapped rasterization. This allows cache lines to represent square tiles, increasing the probability that nearby accesses are in the cache. This is important because 3d rendering involves arbitrary transformations (rotations, scaling, perspective, and distortion by animated surfaces). These are referred to as swizzled textures or twidled textures. Other tiled formats may also be used.

And there's "twiddled" name drop, spelled with one 'd'. Maybe that explains why I couldn't find much searching google for 'twiddled'. But this description makes a lot more sense.

Writing the documentation gave me some ideas for how to simply my functions for parsing textures. Right now I have my twiddled function paired with reading the bytes. What makes more sense is to have a function that takes a square array in twiddled order and then returns it to linear order. Then the schemes for decoding would look like:

Twiddled:

Check for mipmaps, and seek past mipmaps
Read the shorts for the image as a twiddled array
De-twiddle the array
Return the sorted array

VQ:

Check for VQ small to determine the codebook size
Read the codebook
Check for mipmaps, and seek past mipmaps
Read the bytes for the image as a twiddled array
de-twiddled the array
Iterate of over the width and height of image by 2 to create image array from 2x2 codebook entries
Return the sorted array

Palletize 4/8:

Check for mipmaps, and seek past mipmaps
Read the 8 bits / 4 bits of each index in the image as a twiddled array
De-twiddle the array
Replace de-twiddled values with palette colors
Return the sorted array

Rectangle:

Read the shorts for the image and return the array
 
Back
Top