Prev Contents Next

Grit in project builds

2.1. Introduction

Knowing what grit is capable of is only the first step. To use it comfortably, you need to know how to integrate it into the standard build process of your project. In this chapter, I'll explain how to do that. First up is, using grit with or without .grit configuration files. After that, I'll show how to use these rules on the standard template makefiles. Finally, I'll describe how to split off the image conversion to a separate process, keeping the interference to the main makefile to a minimum. Part of this is step is also putting the graphics into an archive (.a) and creating a master header file.

2.2. Running grit

The basic format of a grit command is as follows

grit source files options

The list of options start at the first argument that begins with a hyphen, '-'. You have have one or more source files, but you cannot use wildcards. The output file can be specified with the -o flag, but it is not required. If no options are given, grit will try to guess what you meant from the file itself, but this can be a bit dangerous. It is advised to give at least graphics type (-gb/-gt for bitmaps/tiles), bitdepth (-gB num) and probably output filetype as well (-ft type). Examples:

# Single bitmap
# Input: foo.bmp
# Output: foo.s, 16bpp bitmap as asm array named fooBitmap
grit foo.bmp -fts -gb -gB16

# Sprite sheets (32x32p sprites)
# Input mario.png, luigi.png
# Output: karts.s, 4bpp tiles in 4x4t groups as arrays marioTiles and 
#   luigiTiles, no palette.
grit mario.png luigi.png -o karts.s -gt -gB4 -Mw4 -Mh4 -p!

2.2.1. Simple Makefile Rules

The previous examples would work if you'd enter them on the command-line manually, or via a batchfile. for makefiles, you'd need a little bit extra. The (near) equivalent of the previous examples in makefilese are:

GRIT := grit.exe

# foo.bmp -> foo.s ; fooBitmap = 16bpp bitmap.
foo.s foo.h : foo.bmp
	$(GRIT) $< -fts -gb -gB16

# mario.png, luigi.png -> karts.s ; marioTiles, luigiTiles as 4bpp tiles.
karts.s karts.h : mario.png luigi.png
	$(GRIT) $^ -o$* -fts -gt -gB4 -Mw4 -Mh4 -p!

The terms $<, $^ and $* are some of makefile's automatic variables; they stand for the first prerequisite, all prerequisites and the 'stem' of the targets, respectively. In these rules, that equates to $< = foo.bmp, $^ = mario.png luigi.png and $* = karts.Note that in the second rule, -o$* doesn't add an extension. This is alright because -fts takes care of that.

I should point out that the use of $* is somewhat iffy. Sometimes it works just fine, but in other cases it's empty, which mucks up everything. Usually, $@ works just as well.

2.2.2. Configuration files

Having separate rules for each image doesn't exactly scale well. For this reason, grit also allows you to read the flags from configuration files, usually with the .grit extension. With these files, you can put the image-specific options inside the config file and call them with a generalized rule.

You can indicate the config file with the -ff option. For example, `-ff foo.grit' would read additional options from foo.grit. The config file uses the '#' character for comments, much like makefiles. The config file for foo.bmp would look something like this:

# foo.grit : example of a .grit file

-gb         # Bitmapped output
-gB16       # 16 bpp

The rule to go with this would be something like the following. Here you have a pattern rule that likes a .s and .h file to a .png and .grit file, so that if either the png or grit file is altered, the rule is run again. Technically, there should be a `-ff $*.grit' argument here as well, but grit always looks for an appropriate grit-file automatically, so I can omit it here.

Note that the -fts option is still in the rule itself. The extra options from the .grit-file are simply added to the set of command-line options; they do not replace them.

# Main rule.
%.s %.h	: %.png %.grit
	$(GRIT) $< -fts

This procedure works out nice if you have lots of different options for different files, but when you have a great many images that use the same conversion, you really don't want to have to write grit-files for each image. It would be preferable to have a single grit-file that can be used by all.

In most cases, you'd have to write explicit rules for this because you can't get the name of the grit-file from the names of the images. There is one exception to this, namely when you have a whole directory of images. If you then base the name of the grit-file on the directory name (say, dir/dir.grit), you can use this:

# Using dir.grit for a whole directory.
%.s %.h	: %.png
	$(GRIT) $< -fts -ff $(<D)/$(notdir $(<D)).grit

The tricky part is `$(<D)/$(notdir $(<D)).grit'. This takes the directory part of a source image and turns it into a full path for the grit-file. For example, for /c/stuff/dir/foo.png, it gives /c/stuff/dir/dir.grit.

The single-file rule (`%.png %.grit') and the directory rule (`%.png') can work in the same makefile, but you do have to be little careful with it. When make checks for matching files to rules, it'll use the first rule that gives a match. You need to have the single-file rule before the directory-rule. In that case the single-file rule is used if a png-file does have an accompanying grit-file, but it'll fall back on the directory-rule if it's absent. If you reversed the order of the rules, it'd always use the directory-rule, which is probably not what you'd want.

These rules are for .png files. You can create similar rules of other image types, obviously.

Rule order is important

In a makefile, if a prerequisite or combination of prerequisites follows more than one rule, the first rule that fits is used. So order the grit rules like this:

# With matching grit-file
%.s %.h	: %.png %.grit
	$(GRIT) $< -fts

# No grit-file: try using dir.grit
%.s %.h	: %.png
	$(GRIT) $< -fts -ff $(<D)/$(notdir $(<D)).grit

2.3. Grit in template makefiles

The standard practice for building gba/nds homebrew apps nowadays is to use the template makefiles that come with the devkitPro distribution, or derivatives thereof. These makefiles may have to be altered in several places to make them work for grit. Here is a summary of you may need to do.

Add GRIT and GRAPHICS variables

The GRAPHICS variable is for graphics what SOURCES is for the source files: it's a list of all the directories with graphics. Technically, creating a GRIT variables isn't required, but it can be useful if you have different grit versions around. Add GRIT somewhere at the top, near the inclusion of gba_rules or nds_rules, and GRAPHICS near SOURCES.

include $(DEVKITARM)/ds_rules
GRIT    := grit

# ... stuff ...

TARGET      := $(notdir $(CURDIR))
BUILD       := build
SOURCES     := source
DATA        := data
INCLUDES    := include
GRAPHICS    := gfx

The graphics directory also needs to be added to VPATH:

export VPATH    :=  $(foreach dir, $(SOURCES),  $(CURDIR)/$(dir)) \
                    $(foreach dir, $(DATA),     $(CURDIR)/$(dir)) \
                    $(foreach dir, $(GRAPHICS), $(CURDIR)/$(dir))

Create a GFXFILES variable

Next, we need a list of all the graphics files. This is the analogon of CFILES for the C sources, and it's placed in the same spot.

CFILES      := $(foreach dir, $(SOURCES),  $(notdir $(wildcard $(dir)/*.c)))
CPPFILES    := $(foreach dir, $(SOURCES),  $(notdir $(wildcard $(dir)/*.cpp)))
SFILES      := $(foreach dir, $(SOURCES),  $(notdir $(wildcard $(dir)/*.s)))
BINFILES    := $(foreach dir, $(DATA),     $(notdir $(wildcard $(dir)/*.*)))
GFXFILES    := $(foreach dir, $(GRAPHICS), $(notdir $(wildcard $(dir)/*.png)))

If you want BMPs as well, you can add them here in the same way, only with += instead of :=.

Add GFXFILES to the OFILES list

This is the trickiest step. OFILES lists all the object (.o) files that the build creates, and it will run along these files in the order they're listed. For this reason, the object files of the GFXFILES must occur before those of the cpp/c/s files. If not, make will try to compile/assemble the files that grit creates before they're actually created. yet.

## NOTE: add the GFXFILES line before the sources!!!
export OFILES   :=  $(addsuffix .o,$(BINFILES)) \
                    $(GFXFILES:.png=.o)         \
                    $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)

Add grit rules

Finally, add the rules for grit conversion that I gave before. The best place to add these are at the bottom of the makefile; somewhere after the $(OUTPUT) lines, but before the dependencies.

# With matching grit-file
%.s %.h	: %.png %.grit
	$(GRIT) $< -fts

# No grit-file: try using dir.grit
%.s %.h	: %.png
	$(GRIT) $< -fts -ff $(<D)/$(notdir $(<D)).grit

-include $(DEPENDS)

For other image filetypes, add similar rules with those extensions.

2.4. Grit as a separate process

Adding the rules to the main makefile works, but it has a number of disadvantages. First, of course, is that you have to change the makefile, and this can be tricky in itself for those new to makefiles. Tied to that is the fact that the order of the changes matters as well. Finally, it makes it harder to create specialized rules. The rules I created above process one image at a time, but sometimes you really need to have multiple sources, like when sharing palettes or tilesets.

In many ways, it'll be easier to handle image conversion as a completely separate process, to be done before any real source files are done. Not only are you more in control of the build order, but you can also do all the unseemly steps that you may require in image processing without it upsetting main makefile itself.

In this section, I'll illustrate how you can convert the images in different ways and archive the output into a resource library to be linked into the project like any other lib. I'll also show how you can combine the separate headers into a master header, which makes #including the declarations considerably easier. This is one step that would simply be impossible if you'd use a single makefile.

2.4.1. gfxmake and libgfx

Splitting of the graphics conversion involved the use of a second makefile which I call gfxmake. Here it is in full.

# Making a gfx library from grit output
# For use on data-arrays only. NO CODE!!!

# ---------------------------------------------------------------------
# ---------------------------------------------------------------------

export PATH :=  $(DEVKITARM)/bin:$(PATH)

GRIT        := grit.exe


include $(DEVKITARM)/base_rules

# ---------------------------------------------------------------------
# ---------------------------------------------------------------------

# GFXTITLE  : Graphics library name
# BUILD     : Directory for build process temporaries. Should NOT be empty!
# GFXDIRS   : List of graphics directories
# GFXEXTS   : Graphics extensions.
# General note: use . for the current dir, don't leave them empty.

BUILD       := build
GFXDIRS     := gfx level1 palmerge smkkarts
GFXLIB      ?= libgfx.a
GFXHDR      ?= all_gfx.h

GFXEXTS     := png bmp

# --- Exceptions ---
# Add files/file-variables for special rules here. Put the rules 
# At the bottom of the makefile. Be careful with directories, as
# we'll be in $(BUILD) when converting.
# GFXSPECIALS   : removed from GFXFILES
# OSPECIALS     : added to OFILES

export LOZLEVEL1    ?= $(notdir $(wildcard level1/*.png))
export PALMERGE     ?= $(notdir $(wildcard palmerge/*.png))

# Key exception variables
OSPECIALS           := level1.o palmerge.o

# ---------------------------------------------------------------------
# ---------------------------------------------------------------------

# Since there's no code to compile, we won't need optimizations, 
# architectures etc.

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

# ---------------------------------------------------------------------
# ---------------------------------------------------------------------

ifneq ($(BUILD),$(notdir $(CURDIR)))

# still in main directory.

export TARGET   :=  $(CURDIR)/$(GFXLIB)

export VPATH    :=  $(foreach dir, $(GFXDIRS), $(CURDIR)/$(dir))
export DEPSDIR  :=  $(CURDIR)/$(BUILD)

GFXFILES    := $(filter-out $(GFXSPECIALS),         \
    $(foreach dir, $(GFXDIRS),                      \
        $(foreach ext, $(GFXEXTS),                  \
            $(notdir $(wildcard $(dir)/*.$(ext)))   \

export OFILES   := $(addsuffix .o, $(basename $(GFXFILES))) $(OSPECIALS)

# --- More targets ----------------------------------------------------

.PHONY: $(BUILD) clean

# --- Create BUILD if necessary, and run this makefile from there ---

    @[ -d $@ ] || mkdir -p $@
    @make --no-print-directory -C $(BUILD) -f $(CURDIR)/gfxmake

all : $(BUILD)

    @echo clean ...
    @rm -fr $(BUILD) $(TARGET) $(GFXHDR)

# ---------------------------------------------------------------------


DEPENDS :=  $(OFILES:.o=.d)

.PHONY : all 

all : $(TARGET) $(GFXHDR)

    @echo Archiving into $(notdir $@)
    -@rm -f $@
    @$(AR) rcs $@ $(OFILES)

    @echo "Creating master header: $@"
    @$(call master-header, $@, $(notdir $(^:.o=.h)) )

# ---------------------------------------------------------------------
# ---------------------------------------------------------------------

# --- With separate .grit file ---

%.s %.h : %.png %.grit
    $(GRIT) $< -fts

%.s %.h : %.bmp %.grit
    $(GRIT) $< -fts

%.s %.h : %.pcx %.grit
    $(GRIT) $< -fts

%.s %.h : %.jpg %.grit
    $(GRIT) $< -fts

# --- Without .grit file ; uses dirname/dirname.grit for options ---

%.s %.h : %.png
    $(GRIT) $< -fts -ff $(<D)/$(notdir $(<D)).grit

%.s %.h : %.bmp
    $(GRIT) $< -fts -ff $(<D)/$(notdir $(<D)).grit

%.s %.h : %.pcx
    $(GRIT) $< -fts -ff $(<D)/$(notdir $(<D)).grit

%.s %.h : %.jpg
    $(GRIT) $< -fts -ff $(<D)/$(notdir $(<D)).grit

# ---------------------------------------------------------------------
# ---------------------------------------------------------------------

level1.s level1.h : level1.grit $(LOZLEVEL1)
    @echo $(notdir $^)
    $(GRIT) $(sort $(filter %.png,$^)) -o$@ -ff $<

palmerge.s palmerge.h : palmerge.grit $(PALMERGE)
    @echo $(notdir $^)
    $(GRIT) $(filter %.png,$^) -o$@ -ff $<

# ---------------------------------------------------------------------
# ---------------------------------------------------------------------

## Merge all headers into a single large one for easier including.
define master-header    # $1 : master path, $2 separate header paths
    echo -e "//\n// $(notdir $(strip $1))\n//" > $1
    echo -e "// One header to rule them and in the darkness bind them" >> $1
    echo -e "// Date: $(shell date +'%F %X' )\n" >> $1
    echo -e "#ifdef __cplusplus\nextern \"C\" {\n#endif" >> $1
    cat $2 >> $1
    echo -e "\n#ifdef __cplusplus\n};\n#endif\n" >> $1

## if you just want to include the separate headers, use this instead of cat:
#   for hdr in $2 ; \
#       do echo -e "#include \"$$hdr\"" >> $1 ; done;

# --- odds and ends ---

## Get the title-part of filename.
define title        # $1: filepath
    $(basename $(notdir $1))

## Get a valid C identifier for a name.
define cident       # $1: name
    `echo $1 | sed -e 's|^\([0-9]\)|_\1| ; s|[./\\-]|_|g'`

## Create a header file for a bin2s converted binary.
define bin-header       # $1: path, $2: identifier
    echo "extern const u32 $(strip $2)_size;" >  $1
    echo "extern const u8 $(strip $2)[];"     >> $1 

# ---------------------------------------------------------------------
# ---------------------------------------------------------------------

-include $(DEPENDS)


The gfxmake makefile is divided into a number of sections, of which the five numbered ones are the most important.

  1. Project details.
  2. Build procedure.
  3. Base rules.
  4. Custom rules.
  5. Utility functions.

Project details

Under project details, you can find the most relevant items for the graphics conversions. The BUILD variable is the directory that the converted items go into and GFXDIRS are the directories where the graphics are. The next two variables, GFXLIB and GFXHDR are the names of the graphics library and master header that gfxmake creates. GFXEXTS are the extensions used for the images.

The subsection under exceptions can be ignored for now. The variables given there are excluded from the GFXFILES and OFILES, so that they can be built from custom rules.

Build procedure

Here the variables GFXFILES and OFILES are created, much like it was done in the template makefile. Note that because we're not mixing code and data, the order of listing in OFILES is not important.

Note the use of GFXSPECIALS and OSPECIALS here. The names under GFXSPECIALS are filtered out of GFXFILES because I want their conversion procedure to follow special rules. The object files created in those rules still have to be added to the OFILES, of course, and that's what OSPECIALS is for.

Under the else, right before the Rules section, are the main targets. The target itself is the library file given by GFXLIB, which in this case is called libgfx.a. There is also a rule to make the master header, GFXHDR. The latter rule uses a user defined function master-header, which I'll get to shortly.

Base rules

These are the same rules as before, in the same order. I've already added versions for some other popular graphics types, but more could be added if required. All these rules use assenbly for their output because assembly can be converted to object files a hell of a lot faster than C sources. Since you probably never need to browse through the generated files, assembly should be the better option here.

There is an interesting point that may be worth mentioning about how make goes about using these rules to turn graphics into assembly into objects. The rules here go from .png in PNGFILES to .s, but nowhere are the .s files mentioned explicitly. Only the o. files they become are given (under OFILES). Basically, the .s files are implicit temporaries, and what make does in such a case is to remove the temporaries at the end. Only after running through the rules a second time will the assembly files remain in existence.

It's a bit odd, I know but that's how it is. This can be more than a little annoying if you want to do something else to those files later, so consider yourself warned.

Special rules

The normal rules will convert single images into single assembly/object files. For something like sharing tilesets among different maps, this simply won't do. For such occasions, you have to use explicit rules of some kind.

gfxmake makes this process easier through the GFXSPECIALS and OSPECIALS variables. Files added to GFXSPECIALS won't follow the standard rules, but can be redirected to your own custom rules. In this particular case, I have all the rooms from the first dungeon in Zelda 1 in the level1 directory, and I want to convert the maps with a shared tileset. The steps of interest for that are the following.

# Create a variable with the level1 filenames
export LOZLEVEL1    ?= $(notdir $(wildcard level1/*.png))

# Add the sources to GFXSPECIALS and the output file to OSPECIALS
OSPECIALS           := level1.o

# Rule to for the conversion
level1.s level1.h : level1.grit $(LOZLEVEL1)
    @echo $(notdir $^)
    $(GRIT) $(sort $(filter %.png,$^)) -o$@ -ff $<

Once GFXSPECIALS and OSPECIALS are set up, you can do with those files whatever you like. The custom rule looks similar to rule for the karts from before, except that now there is a grit-file as well. Unfortunately, I do need some makefile trickery to get it all done. To get the list of sources from the prerequisites, I need to filter for .pngs only. The sort is somewhat optional, but recommended as well. Another thing I'm doing here is to place the grit-file first in the list. This enables me to get its name with $<, rather than having to filter it out as well.

Utility functions

make allows you to create your own functions as well. These can help in doing special things. Though the others are useful as well, the only one I make actual use of in this gfxmake is master-header:

# Merge all headers into a single large one for easier including.
define master-header    # $1 : master path, $2 separate header paths
    echo -e "//\n// $(notdir $(strip $1))\n//" > $1
    echo -e "// One header to rule them and in the darkness bind them" >> $1
    echo -e "// Date: $(shell date +'%F %X' )\n" >> $1
    echo -e "#ifdef __cplusplus\nextern \"C\" {\n#endif" >> $1
    cat $2 >> $1
    echo -e "\n#ifdef __cplusplus\n};\n#endif\n" >> $1

# Usage example 
    @echo "Creating master header: $@"
    @$(call master-header, $@, $(notdir $(^:.o=.h)) )

The master-header function takes two arguments: the path of the master header that's to be created, and a list of the original headers. It will create a single header file with a tile-stamped preface and then copies all the separate headers into it. This means that the output of gfxmake is a combination of one header and one archive, which can be put to use with more ease than a bucketful of separate files.

2.4.2. Modifications to the template

Of course, even with gfxmake you still need some changes to the template makefile, but they're smaller than adding all of gfxmake functionality into the template. There are two main changes to make.

The first is to add make libgfx.a and its headers visible. This is done like adding any other library: add -lgfx to the LIBS variable. Technically you should add the appropriate paths to LIBDIRS as well, but since because the files are in the current dir and BUILD, this isn't really necessary.

The second is to invoke gfxmake from the main makefile. This comes down to adding a line that calls it to the build rule. The whole thing comes down to something like this.

## Create a gfx library variable
GFXLIBS     ?= libgfx.a

## Add it to the library list.
LIBS        := standard libs -lgfx

## Make the .elf depend on the graphics lib(s)
$(OUTPUT).elf   :   $(OFILES) $(GFXLIBS)

## Add line to invoke gfxmake
    @[ -d $@ ] || mkdir -p $@
    @make --no-print-directory -f $(CURDIR)/gfxmake
    @make --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile

And that's about it. I think it should be possible to make it even easier, but this should be enough for now.

2.5. Conclusions

Getting grit to work inside your makefile shouldn't be too difficult if you follow these steps. The main thing to remember is to use configuration files and the following makefile rules:

# With matching grit-file
%.s %.h	: %.png %.grit
	$(GRIT) $< -fts

# No grit-file: try using dir.grit
%.s %.h	: %.png
	$(GRIT) $< -fts -ff $(<D)/$(notdir $(<D)).grit

With these rules, you can convert single image either by using a similarly named grit-file, or share a grit-file for a whole directory by naming the grit-file the same basename as the directory. Adding explicit rules is also an option, of course.

You also need to add a few variables to the makefiles for bookkeeping purposes. These have to be added very carefully, because the order sometimes matters. An alternative is to pass the conversion process to another script (makefile or otherwise). This can probably save you a lot of hassle in the long run, which is why I'm using that method in grit's demo project. In the end, of course, what you use is up to you.

Modified May 4 2008, J Vijn. Get grit here