KMG Textures

From DCEmulation
Jump to: navigation, search

About KMG files

There are many image file formats out there.

First of all the image data may be encoded in various ways. For example the pixel format could be RGB565 or ARGB4444. Furthermore the image data may be paletted, twiddled or compressed.

The image may also come with mipmaps (smaller versions of itself). The mipmaps could be stored from small to big or big to small, with padding, etc.

Lastly, meta information such as the dimensions, used pixel format, how many mipmaps are available, whether compression was applied, etc. must be stored somehow.

Therefore, instead of just storing the raw image data, so called container formats are used. KOS provides an image container format called KMG.

You are in no way forced to use this format and may want to create your own for advanced use cases, but it gets the basic job done and comes with convenient functions and a conversion program in KOS.

This tutorial will explain how to convert images to Dreamcast-friendly formats and pack them into a KMG container file. It will then explain how to load the data into memory, ready to be used in drawing code.

Conversion

Directory setup

In your project's folder, you should create a folder named assets and another one named romdisk (names can be changed).

assets will contain your raw image files. assets/foo.png will be converted to romdisk/foo.kmg.

The raw asset files are put into a different directory, because only the converted files will be used in the game and the raw asset files would only take up space.

Makefile rules

Makefiles describe a dependency graph to only rebuild files that were changed. This is useful because converting all our image files all the time, although they haven't changed, would take a lot of build time.

In our case the dependency graph for our game might look like the following:

 image1.png --> image1.kmg -\
 image2.png --> image2.kmg --+--> romdisk.img --> romdisk.o --+--> game.elf
 image3.png --> image3.kmg -/          main.c --> main.o ----/

So when image2.png is changed by an artist, only image2.kmg is regenerated using the vqenc conversion tool, which leads to regenerating romdisk.img which leads to regenerating romdisk.o.

Let's look at the Makefile rules to describe this process starting from the back.

Makefile rule syntax

The general syntax is

 target_file: dependency_file1 dependency_file2
 <TAB>shell commands

For a full introduction to Make please refer to [1]

romdisk.o rule

This rule will convert romdisk.img to romdisk.o which can be passed to gcc during compilation (kos-cc main.o romdisk.o -o game.elf)

romdisk.o: romdisk.img
       $(KOS_BASE)/utils/bin2o/bin2o romdisk.img romdisk romdisk.o

romdisk.img rule

This rule gathers a list of filenames of the form assets/*.png. It will then convert this list into the form romdisk/*.kmg. The generated kmg filename list is then used as the list of required files for romdisk.img.

genromfs is told to put the directory romdisk into romdisk.img

romdisk.img: $(patsubst assets/%.png,romdisk/%.kmg,$(wildcard assets/*.png))
       $(KOS_GENROMFS) -f romdisk.img -d romdisk -v

The converted image files will be available as /rd/foo.kmg during runtime.

ELF building rule

This rule gathers up all of the built object files of the program and links them with any necessary libraries to produce a .elf file that can be run.

First, define the target for your build and all the object files that are required for the binary. Put this little bit of setup work up at the top of your Makefile:

$(TARGET) = test.elf
$(OBJS) = main.o romdisk.o

all: rm-elf $(TARGET)

include $(KOS_BASE)/Makefile.rules

rm-elf:
        -rm -f $(TARGET) romdisk.*

Then, add the build target to create your actual binary from the built object files, like so:

$(TARGET): $(OBJS)
        kos-cc -o $(TARGET) $(OBJS) -lkmg -lkosutils

You will need to build libkmg in kos-ports if you have not already done so in order to build your program.

PNG to KMG conversion rule

Next we need to call a program to actually convert foo.png to foo.kmg. KOS provides an image data conversion tool that can optionally put the resulting data in the KMG container format. You can find it in the folder kos/utils/vqenc.

Usage: vqenc [options] image1 [image2..]
Options:
       -t, --twiddle   create twiddled textures
       -m, --mipmap    create mipmapped textures (EXPERIMENTAL)
       -v, --verbose   verbose
       -d, --debug     show debug information
       -q, --highq     higher quality (much slower)
       -k, --kmg       write a KMG for output
       -a, --alpha     use alpha channel (and output ARGB4444)
       -b, --amask     use 1-bit alpha mask (and output ARGB1555)

The following Makefile rule should be added to use it to convert images:

romdisk/%.kmg: assets/%.png
       $(KOS_BASE)/utils/vqenc/vqenc -v -t -q -k $<
       mv assets/$*.kmg romdisk/

-v will turn on verbose conversion information and give you some additional insight.

-t will reorder the pixels into the order the PVR graphics chip can render the fastest.

-q will tell the conversion program that it can take its time to perform a better VQ compression.

-k will save the converted and compressed image data in a .kmg file. Otherwise it will create a .vq file without meta information which you could use in your own image container format.


Since vqenc does not accept a destination directory name, the result is moved to the romdisk directory manually. In Make, $* represents the result of the % placeholder in the rule line.

After typing make you should see that foo.kmg is put into the romdisk, but foo.png is not.

Loading

To gain access to the romdisk and read files in the romdisk directory as /rd/foo.kmg, you need to add the following:

extern uint8 romdisk[];
KOS_INIT_ROMDISK(romdisk);

The loading function will load the texture and put its content into video memory.

#include <kos/img.h>
#include <kmg/kmg.h>
#include <dc/pvr.h>
#include <stdio.h>

pvr_ptr_t load_kmg(char const* filename, uint32* w, uint32* h, uint32* format) {
       kos_img_t img;
       pvr_ptr_t rv;
       if(kmg_to_img(filename, &img)) {
               printf("Failed to load image file: %s\n", filename);
               return NULL;
       }
       if(!(rv = pvr_mem_malloc(img.byte_count))) {
               printf("Couldn't allocate memory for texture!\n");
               kos_img_free(&img, 0);
               return NULL;
       }
       pvr_txr_load_kimg(&img, rv, 0);
       kos_img_free(&img, 0);
       *w = img.w;
       *h = img.h;
       *format = img.fmt;
       return rv;
}

Usage in main():

 int main() {
       /* ... */
       uint32 w, h, format;
       pvr_ptr_t txr = load_kmg("/rd/image.kmg", &w, &h, &format);
       if(txr)
         printf("Loaded /rd/image.kmg with dimensions %ux%u, format %u\n", w, h, format);
 }

You can now use the PVR API to draw this texture.