<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://dreamcast.wiki/wiki/index.php?action=history&amp;feed=atom&amp;title=PVR_Spritesheets</id>
	<title>PVR Spritesheets - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://dreamcast.wiki/wiki/index.php?action=history&amp;feed=atom&amp;title=PVR_Spritesheets"/>
	<link rel="alternate" type="text/html" href="https://dreamcast.wiki/wiki/index.php?title=PVR_Spritesheets&amp;action=history"/>
	<updated>2026-05-09T15:38:53Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.39.3</generator>
	<entry>
		<id>https://dreamcast.wiki/wiki/index.php?title=PVR_Spritesheets&amp;diff=363&amp;oldid=prev</id>
		<title>Unknown user at 03:51, 11 January 2020</title>
		<link rel="alternate" type="text/html" href="https://dreamcast.wiki/wiki/index.php?title=PVR_Spritesheets&amp;diff=363&amp;oldid=prev"/>
		<updated>2020-01-11T03:51:21Z</updated>

		<summary type="html">&lt;p&gt;&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;en&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Older revision&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Revision as of 03:51, 11 January 2020&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot; id=&quot;mw-diff-left-l1&quot;&gt;Line 1:&lt;/td&gt;
&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;Line 1:&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-deleted&quot;&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;Author: Bogglez&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-deleted&quot;&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;= About this tutorial =&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;= About this tutorial =&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;In this tutorial you will learn:&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;In this tutorial you will learn:&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;</summary>
		<author><name>Unknown user</name></author>
	</entry>
	<entry>
		<id>https://dreamcast.wiki/wiki/index.php?title=PVR_Spritesheets&amp;diff=350&amp;oldid=prev</id>
		<title>Unknown user: Darc moved page Wiki/PVR Spritesheets to PVR Spritesheets</title>
		<link rel="alternate" type="text/html" href="https://dreamcast.wiki/wiki/index.php?title=PVR_Spritesheets&amp;diff=350&amp;oldid=prev"/>
		<updated>2020-01-11T03:41:33Z</updated>

		<summary type="html">&lt;p&gt;Darc moved page &lt;a href=&quot;/Wiki/PVR_Spritesheets&quot; class=&quot;mw-redirect&quot; title=&quot;Wiki/PVR Spritesheets&quot;&gt;Wiki/PVR Spritesheets&lt;/a&gt; to &lt;a href=&quot;/PVR_Spritesheets&quot; title=&quot;PVR Spritesheets&quot;&gt;PVR Spritesheets&lt;/a&gt;&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;en&quot;&gt;
				&lt;td colspan=&quot;1&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Older revision&lt;/td&gt;
				&lt;td colspan=&quot;1&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Revision as of 03:41, 11 January 2020&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-notice&quot; lang=&quot;en&quot;&gt;&lt;div class=&quot;mw-diff-empty&quot;&gt;(No difference)&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</summary>
		<author><name>Unknown user</name></author>
	</entry>
	<entry>
		<id>https://dreamcast.wiki/wiki/index.php?title=PVR_Spritesheets&amp;diff=343&amp;oldid=prev</id>
		<title>Unknown user: Created page with &quot;= About this tutorial = In this tutorial you will learn: * Generating a spritesheet from a directory full of single images automatically. * Converting the spritesheet into a P...&quot;</title>
		<link rel="alternate" type="text/html" href="https://dreamcast.wiki/wiki/index.php?title=PVR_Spritesheets&amp;diff=343&amp;oldid=prev"/>
		<updated>2020-01-11T03:33:59Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;= About this tutorial = In this tutorial you will learn: * Generating a spritesheet from a directory full of single images automatically. * Converting the spritesheet into a P...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;= About this tutorial =&lt;br /&gt;
In this tutorial you will learn:&lt;br /&gt;
* Generating a spritesheet from a directory full of single images automatically.&lt;br /&gt;
* Converting the spritesheet into a PVR paletted texture automatically.&lt;br /&gt;
* Loading the spritesheet into the PVR for drawing.&lt;br /&gt;
* Setting up the palette for drawing.&lt;br /&gt;
* Drawing a user interface (with a dynamic health bar).&lt;br /&gt;
* Drawing animated characters.&lt;br /&gt;
* Using the PVR&amp;#039;s sprite drawing mode (instead of polygons)&lt;br /&gt;
&lt;br /&gt;
This will be the end result:&lt;br /&gt;
&lt;br /&gt;
[[File:Spritesheet_tutorial.gif]]&lt;br /&gt;
&lt;br /&gt;
= Required software =&lt;br /&gt;
Before you can get started, you will need to install two tools.&lt;br /&gt;
&lt;br /&gt;
== TexturePacker ==&lt;br /&gt;
The first tool you need is [https://www.codeandweb.com/texturepacker/download TexturePacker]. It packs single images together into one big spritesheet, so you don&amp;#039;t need to do this manually. We will make it do it for us using Make.&lt;br /&gt;
&lt;br /&gt;
TexturePacker has some Pro features, but this tutorial limits itself to its free features.&lt;br /&gt;
&lt;br /&gt;
This image should give you an idea about this tools&amp;#039;s usefulness:&lt;br /&gt;
&lt;br /&gt;
[[File:Spritesheet_tutorial_texturepacker.png]]&lt;br /&gt;
&lt;br /&gt;
It will also create text files of the following format:&lt;br /&gt;
&lt;br /&gt;
 mage_combat6, 244, 103, 122, 103, 0, 0, 0, 0&lt;br /&gt;
 mage_combat7, 366, 103, 122, 103, 0, 0, 0, 0&lt;br /&gt;
 mage_idle0, 0, 206, 79, 93, 0, 0, 0, 0&lt;br /&gt;
 mage_idle1, 79, 206, 79, 94, 0, 0, 0, 0&lt;br /&gt;
 mage_idle2, 158, 206, 82, 94, 0, 0, 0, 0&lt;br /&gt;
 mage_idle3, 240, 206, 85, 94, 0, 0, 0, 0&lt;br /&gt;
&lt;br /&gt;
The example code will parse this information to find out where each sprite within the spritesheet is.&lt;br /&gt;
&lt;br /&gt;
== texconv ==&lt;br /&gt;
The next tool you need is [https://github.com/tvspelsfreak/texconv texconv] by tvspelsfreak.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;texconv&amp;#039;&amp;#039; is an alternative to KOS&amp;#039; &amp;#039;&amp;#039;vqenc&amp;#039;&amp;#039; for converting images such as PNG into texture formats supported by the Dreamcast&amp;#039;s PVR graphics chip. It doesn&amp;#039;t create KMG files, instead it creates its own DTEX format, but provides other nice features. For this tutorial paletted textures are essential.&lt;br /&gt;
&lt;br /&gt;
Another feature that is used is its preview feature. It will basically convert the converted texture back to PNG so you can check the quality of the conversion.&lt;br /&gt;
&lt;br /&gt;
texconv has a dependency on Qt5, so install that first.&lt;br /&gt;
&lt;br /&gt;
On Debian-based systems this will get you running:&lt;br /&gt;
 sudo apt install qt5-default qtbase5-dev&lt;br /&gt;
 git clone https://github.com/tvspelsfreak/texconv&lt;br /&gt;
 cd texconv&lt;br /&gt;
 qmake&lt;br /&gt;
 make&lt;br /&gt;
&lt;br /&gt;
You can then use &amp;#039;&amp;#039;./texconv&amp;#039;&amp;#039;. I suggest you put it somewhere in your &amp;#039;&amp;#039;PATH&amp;#039;&amp;#039; for convenient global access.&lt;br /&gt;
&lt;br /&gt;
texconv offers the following options (options used in this tutorial are marked as &amp;#039;&amp;#039;&amp;#039;bold&amp;#039;&amp;#039;&amp;#039;):&lt;br /&gt;
&lt;br /&gt;
 Usage: ./texconv [options]&lt;br /&gt;
 Texture formats:&lt;br /&gt;
  BUMPMAP  PAL4BPP  ARGB4444  YUV422  ARGB1555  RGB565  &amp;#039;&amp;#039;&amp;#039;PAL8BPP&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
 Options:&lt;br /&gt;
  -h, --help                Displays this help.&lt;br /&gt;
  &amp;#039;&amp;#039;&amp;#039;-i, --in &amp;lt;filename&amp;gt;       Input file(s). (REQUIRED)&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
  &amp;#039;&amp;#039;&amp;#039;-o, --out &amp;lt;filename&amp;gt;      Output file. (REQUIRED)&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
  &amp;#039;&amp;#039;&amp;#039;-f, --format &amp;lt;format&amp;gt;     Texture format. (REQUIRED)&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
  -m, --mipmap              Generate/allow mipmaps.&lt;br /&gt;
  -c, --compress            Output a compressed texture.&lt;br /&gt;
  -s, --stride              Output a stride texture.&lt;br /&gt;
  &amp;#039;&amp;#039;&amp;#039;-p, --preview &amp;lt;filename&amp;gt;  Generate a texture preview.&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
  -v, --verbose             Extra printouts.&lt;br /&gt;
  -n, --nearest             Use nearest-neighbor filtering for scaling mipmaps.&lt;br /&gt;
  -b, --bilinear            Use bilinear filtering for scaling mipmaps.&lt;br /&gt;
  --vqcodeusage &amp;lt;filename&amp;gt;  Output an image that visualizes compression code&lt;br /&gt;
                            usage.&lt;br /&gt;
&lt;br /&gt;
= Downloading and running the example code =&lt;br /&gt;
After installing the required software, you can download [[:File:Kos spritesheet tutorial.zip|the example source code for this tutorial]].&lt;br /&gt;
&lt;br /&gt;
Unzip the archive and open the directory &amp;#039;&amp;#039;kos_spritesheet_tutorial&amp;#039;&amp;#039;. You will find the following directory and file structure:&lt;br /&gt;
* &amp;#039;&amp;#039;assets&amp;#039;&amp;#039;: Raw art assets that will be turned into spritesheets and converted to Dreamcast PVR textures.&lt;br /&gt;
* &amp;#039;&amp;#039;build&amp;#039;&amp;#039;: Contains temporary files. For example &amp;#039;&amp;#039;build/ui_sheet.png&amp;#039;&amp;#039; will contain all art assets from &amp;#039;&amp;#039;assets/spritesheets/ui/*&amp;#039;&amp;#039;.&lt;br /&gt;
* &amp;#039;&amp;#039;main.c&amp;#039;&amp;#039;: Loading, parsing and drawing code.&lt;br /&gt;
* &amp;#039;&amp;#039;Makefile&amp;#039;&amp;#039;: Build system. Also calls &amp;#039;&amp;#039;TexturePacker&amp;#039;&amp;#039; and &amp;#039;&amp;#039;texconv&amp;#039;&amp;#039; to create the spritesheets automatically upong changing anything.&lt;br /&gt;
* &amp;#039;&amp;#039;readme.txt&amp;#039;&amp;#039;: License for the used artwork.&lt;br /&gt;
* &amp;#039;&amp;#039;romdisk&amp;#039;&amp;#039;: Contains the files that will be available at runtime of the game.&lt;br /&gt;
&lt;br /&gt;
After typing &amp;#039;&amp;#039;make&amp;#039;&amp;#039;, you should get the following output:&lt;br /&gt;
 kos-cc -std=c11 -c main.c -o main.o&lt;br /&gt;
 &lt;br /&gt;
 Generating spritesheet build/stage1_actors_sheet.png from assets/spritesheets/stage1_actors&lt;br /&gt;
 TexturePacker --sheet build/stage1_actors_sheet.png \&lt;br /&gt;
        --format gideros --data romdisk/stage1_actors_sheet.txt \&lt;br /&gt;
        --size-constraints POT --max-width 1024 --max-height 1024 \&lt;br /&gt;
        --pack-mode Best --disable-rotation --trim-mode None \&lt;br /&gt;
        --trim-sprite-names \&lt;br /&gt;
        --algorithm Basic --png-opt-level 0 --extrude 0 --disable-auto-alias \&lt;br /&gt;
        assets/spritesheets/stage1_actors&lt;br /&gt;
 Resulting sprite sheet is 512x1024&lt;br /&gt;
 Writing sprite sheet to build/stage1_actors_sheet.png&lt;br /&gt;
 Writing romdisk/stage1_actors_sheet.txt&lt;br /&gt;
 &lt;br /&gt;
 Converting romdisk/stage1_actors_sheet.dtex &amp;lt; build/stage1_actors_sheet.png&lt;br /&gt;
 texconv -i build/stage1_actors_sheet.png -o romdisk/stage1_actors_sheet.dtex -f PAL8BPP -p build/stage1_actors_preview.png&lt;br /&gt;
 &lt;br /&gt;
 Generating spritesheet build/ui_sheet.png from assets/spritesheets/ui&lt;br /&gt;
 TexturePacker --sheet build/ui_sheet.png \&lt;br /&gt;
        --format gideros --data romdisk/ui_sheet.txt \&lt;br /&gt;
        --size-constraints POT --max-width 1024 --max-height 1024 \&lt;br /&gt;
        --pack-mode Best --disable-rotation --trim-mode None \&lt;br /&gt;
        --trim-sprite-names \&lt;br /&gt;
        --algorithm Basic --png-opt-level 0 --extrude 0 --disable-auto-alias \&lt;br /&gt;
        assets/spritesheets/ui&lt;br /&gt;
 Resulting sprite sheet is 512x128&lt;br /&gt;
 Writing sprite sheet to build/ui_sheet.png&lt;br /&gt;
 Writing romdisk/ui_sheet.txt&lt;br /&gt;
 &lt;br /&gt;
 Converting romdisk/ui_sheet.dtex &amp;lt; build/ui_sheet.png&lt;br /&gt;
 texconv -i build/ui_sheet.png -o romdisk/ui_sheet.dtex -f PAL8BPP -p build/ui_preview.png&lt;br /&gt;
 &lt;br /&gt;
 /opt/toolchains/dc/kos/utils/genromfs/genromfs -f romdisk.img -d romdisk -v&lt;br /&gt;
 0    rom 56ef0c99         [0xffffffff, 0xffffffff] 37777777777, sz     0, at 0x0     &lt;br /&gt;
 1    .                    [0x802     , 0x566411  ] 0040755, sz     0, at 0x20    &lt;br /&gt;
 1    ..                   [0x802     , 0x56639f  ] 0040755, sz     0, at 0x40     [link to 0x20    ]&lt;br /&gt;
 1    ui_sheet.dtex.pal    [0x802     , 0x5654fa  ] 0100644, sz  1032, at 0x60    &lt;br /&gt;
 1    ui_sheet.txt         [0x802     , 0x5654f7  ] 0100644, sz   392, at 0x4a0   &lt;br /&gt;
 1    stage1_actors_sheet.dtex [0x802     , 0x5654f3  ] 0100644, sz 524304, at 0x650   &lt;br /&gt;
 1    stage1_actors_sheet.dtex.pal [0x802     , 0x5654f4  ] 0100644, sz   104, at 0x80690 &lt;br /&gt;
 1    stage1_actors_sheet.txt [0x802     , 0x5654f2  ] 0100644, sz  1818, at 0x80730 &lt;br /&gt;
 1    ui_sheet.dtex        [0x802     , 0x5654f8  ] 0100644, sz 65552, at 0x80e80 &lt;br /&gt;
 /opt/toolchains/dc/kos/utils/bin2o/bin2o romdisk.img romdisk romdisk.o&lt;br /&gt;
 &lt;br /&gt;
 kos-cc -o spritesheet.elf main.o romdisk.o -lkmg -lkosutils -lm&lt;br /&gt;
&lt;br /&gt;
The executable will be called &amp;#039;&amp;#039;spritesheet.elf&amp;#039;&amp;#039; and can be run in an emulator or on your Dreamcast.&lt;br /&gt;
&lt;br /&gt;
= Explanation =&lt;br /&gt;
The Makefile and main.c are heavily commented, so I will only refer to the most important bits here.&lt;br /&gt;
&lt;br /&gt;
== Makefile ==&lt;br /&gt;
If any of these rules confuse you, compare them to the output of the &amp;#039;&amp;#039;make&amp;#039;&amp;#039; command above and things should become clear.&lt;br /&gt;
&lt;br /&gt;
￼The Makefile target spritesheet.elf (the executable of the game) depends on romdisk.o, which depends on romdisk.img.&lt;br /&gt;
&lt;br /&gt;
All the converted spritesheets, their palettes, and the sprite geometry info will be stored in this romdisk, therefore romdisk.img must depend on those files. We don&amp;#039;t care about the raw art assets, however, since they&amp;#039;re not used at runtime.&lt;br /&gt;
&lt;br /&gt;
To achieve this in Make, the name of all spritesheets needs to be generated first, i.e. &amp;#039;&amp;#039;assets/spritesheets/foo/*.png assets/spritesheets/bar/*.png&amp;#039;&amp;#039; needs to turn into &amp;#039;&amp;#039;foo bar&amp;#039;&amp;#039;&lt;br /&gt;
 SPRITESHEET_NAMES := $(notdir $(wildcard assets/spritesheets/*))&lt;br /&gt;
&lt;br /&gt;
Now &amp;#039;&amp;#039;romdisk.img&amp;#039;&amp;#039; should depend on &amp;#039;&amp;#039;romdisk/foo_sheet.dtex&amp;#039;&amp;#039; and &amp;#039;&amp;#039;romdisk/bar_sheet.dtex&amp;#039;&amp;#039;. But &amp;#039;&amp;#039;texconv&amp;#039;&amp;#039; will save the palette file separately, so we add &amp;#039;&amp;#039;romdisk/foo_sheet.dtex.pal&amp;#039;&amp;#039; etc.&lt;br /&gt;
 SPRITESHEET_RESULT_FILES := $(patsubst %,romdisk/%_sheet.dtex,$(SPRITESHEET_NAMES))&lt;br /&gt;
 romdisk.img: $(SPRITESHEET_RESULT_FILES) $(addsuffix .pal,$(SPRITESHEET_RESULT_FILES))&lt;br /&gt;
 	$(KOS_GENROMFS) -f romdisk.img -d romdisk -v&lt;br /&gt;
&lt;br /&gt;
Next Make needs to know how to generate the dtex and dtex.pal files from the corresponding PNG using &amp;#039;&amp;#039;texconv&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
 romdisk/%_sheet.dtex romdisk/%_sheet.dtex.pal: build/%_sheet.png&lt;br /&gt;
 	$(info Converting $@ &amp;lt; $&amp;lt;)&lt;br /&gt;
 	texconv -i $&amp;lt; -o $@ -f PAL8BPP -p build/$*_preview.png&lt;br /&gt;
&lt;br /&gt;
This rule requires that the sprites have been assembled as &amp;#039;&amp;#039;build/foo_sheet.png&amp;#039;&amp;#039; before (using TexturePacker), so that file becomes a dependency.&lt;br /&gt;
&lt;br /&gt;
Notice how the texture format is set to PAL8BPP, allowing for 256 different colors in the palette. PAL4BPP is also a possibility. texconv assumes 32 bit colors in the palette. It implicitly creates the &amp;#039;&amp;#039;.dtex.pal&amp;#039;&amp;#039; file.&lt;br /&gt;
&lt;br /&gt;
Also take a look at the &amp;#039;&amp;#039;build/foo_preview.png&amp;#039;&amp;#039; file to see whether you suffered quality loss during the conversion. This will happen if your input image has more than 256 colors in this case.&lt;br /&gt;
&lt;br /&gt;
Lastly TexturePacker is called to turn all images in the directory &amp;#039;&amp;#039;assets/spritesheets/foo/&amp;#039;&amp;#039; into &amp;#039;&amp;#039;build/foo_sheet.png&amp;#039;&amp;#039; and generate &amp;#039;&amp;#039;romdisk/foo_sheet.txt&amp;#039;&amp;#039; with the sprite geometry information. The txt file is stored in &amp;#039;&amp;#039;romdisk/&amp;#039;&amp;#039; because we will parse it during runtime in order to draw the sprites. &amp;#039;&amp;#039;build/foo_sheet.png&amp;#039;&amp;#039; is just the temporary file to create the Dreamcast PVR texture &amp;#039;&amp;#039;romdisk/foo_sheet.dtex&amp;#039;&amp;#039; (and &amp;#039;&amp;#039;.pal&amp;#039;&amp;#039;).&lt;br /&gt;
&lt;br /&gt;
TexturePacker is instructed to create power-of-two textures (e.g. 64x256) of maximum dimensions 1024x1024, because of the PVR graphics chip&amp;#039;s limitations.&lt;br /&gt;
&lt;br /&gt;
The options in the last line are necessary to disable pro-version features (otherwise TexturePacker will turn some sprites red).&lt;br /&gt;
 build/%_sheet.png: assets/spritesheets/%&lt;br /&gt;
 	$(info Generating spritesheet $@ from $^)&lt;br /&gt;
 	TexturePacker --sheet $@ \&lt;br /&gt;
 		--format gideros --data romdisk/$*_sheet.txt \&lt;br /&gt;
 		--size-constraints POT --max-width 1024 --max-height 1024 \&lt;br /&gt;
 		--pack-mode Best --disable-rotation --trim-mode None \&lt;br /&gt;
 		--trim-sprite-names \&lt;br /&gt;
 		--algorithm Basic --png-opt-level 0 --extrude 0 --disable-auto-alias \&lt;br /&gt;
 		$^&lt;br /&gt;
&lt;br /&gt;
== The code ==&lt;br /&gt;
I&amp;#039;ll explain the code top-down, to give you a high level view. So assume that the mentioned helper functions have already been written, they will be explained later.&lt;br /&gt;
&lt;br /&gt;
=== The main loop ===&lt;br /&gt;
The main loop is very basic:&lt;br /&gt;
&lt;br /&gt;
 struct spritesheet stage1_actors_sheet, ui_sheet;&lt;br /&gt;
 int main(int argc, char *argv[]) {&lt;br /&gt;
 	int result = 0;&lt;br /&gt;
 	if(init()) {&lt;br /&gt;
 		puts(&amp;quot;Cannot init&amp;quot;);&lt;br /&gt;
 		result = 1;&lt;br /&gt;
 		goto cleanup;&lt;br /&gt;
 	}&lt;br /&gt;
 	for(;;) {&lt;br /&gt;
 		draw();&lt;br /&gt;
 	}&lt;br /&gt;
 cleanup:&lt;br /&gt;
 	spritesheet_free(&amp;amp;stage1_actors_sheet);&lt;br /&gt;
 	spritesheet_free(&amp;amp;ui_sheet);&lt;br /&gt;
 	return result;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Two spritesheets are loaded within init() as global variables. Then draw() will draw some sprites on the screen repeatedly. In the end some clean-up code is called (here only if init() fails).&lt;br /&gt;
&lt;br /&gt;
=== The drawing routine ===&lt;br /&gt;
This is the draw() function.&lt;br /&gt;
&lt;br /&gt;
First it needs to wait for the PVR graphics chip to be ready for drawing:&lt;br /&gt;
 static void draw() {&lt;br /&gt;
 	pvr_wait_ready(); /* Await v-blank */&lt;br /&gt;
 	pvr_scene_begin();&lt;br /&gt;
&lt;br /&gt;
Next, the PVR is told to expect a punchthru polygon list. This will allow you to send geometry that can have translucency per pixel (visible or not, not half, unlike transparency).&lt;br /&gt;
&lt;br /&gt;
If you want to send opaque or transparent polygons too, you need to do that before this code. This function implicitly closes the preceding polygon lists for the duration of this frame.&lt;br /&gt;
 	pvr_list_begin(PVR_LIST_PT_POLY);&lt;br /&gt;
&lt;br /&gt;
Now we set up the palettes for this frame. We tell the PVR chip what colors the textures refer to (try mixing them up for fun!). Here, draw() uses my helper function setup_palette():&lt;br /&gt;
 void setup_palette(uint32_t const * colors, uint16_t count, uint8_t palette_number);&lt;br /&gt;
&lt;br /&gt;
 	// in draw()&lt;br /&gt;
 	uint8_t const ui_palette_number = 0;&lt;br /&gt;
 	uint8_t const stage1_actors_palette_number = 1;&lt;br /&gt;
 	setup_palette(ui_sheet.palette, ui_sheet.color_count, ui_palette_number);&lt;br /&gt;
 	setup_palette(stage1_actors_sheet.palette, stage1_actors_sheet.color_count, stage1_actors_palette_number);&lt;br /&gt;
&lt;br /&gt;
Now the actual sprite drawing happens. We draw a little user interface and some animated characters using my helper function draw_sprite():&lt;br /&gt;
 void draw_sprite(struct spritesheet const * const sheet, char const * const name, float x, float y, uint8_t palette_number);&lt;br /&gt;
&lt;br /&gt;
The arguments are&lt;br /&gt;
* a spritesheet struct with parsed information,&lt;br /&gt;
* the name of the sprite inside of it that should be drawn (e.g. &amp;#039;&amp;#039;healthbar_left&amp;#039;&amp;#039; in &amp;#039;&amp;#039;romdisk/ui_sheet.txt&amp;#039;&amp;#039;)&lt;br /&gt;
* x, y coordinates on screen (y goes downwards)&lt;br /&gt;
* The number of the palette to use. Palettes on the Dreamcast persist throughout a frame, so you cannot just switch the palette after drawing a texture. If you want to use multiple paletted textures with different palettes, you will have to put their palette into a different part of the global palette. The global palette has 1024 entries, 8-bit paletted textures use 256 entries each.&lt;br /&gt;
&lt;br /&gt;
The KOS function &amp;#039;&amp;#039;timer_ms_gettime64()&amp;#039;&amp;#039; is used for animation of the characters depending on how much time has elapsed.&lt;br /&gt;
 	/* Draw UI */&lt;br /&gt;
 	draw_sprite(&amp;amp;ui_sheet, &amp;quot;stage_announce&amp;quot;, 174, 100, ui_palette_number);&lt;br /&gt;
 &lt;br /&gt;
 	/* Health bar background */&lt;br /&gt;
 	draw_sprite(&amp;amp;ui_sheet, &amp;quot;barbg_left&amp;quot;,  10, 10, 0);&lt;br /&gt;
 	for(int x = 0; x &amp;lt; 100; ++x)&lt;br /&gt;
 		draw_sprite(&amp;amp;ui_sheet, &amp;quot;barbg_mid&amp;quot;, 15+x, 10, 0);&lt;br /&gt;
 	draw_sprite(&amp;amp;ui_sheet, &amp;quot;barbg_right&amp;quot;, 115, 10, 0);&lt;br /&gt;
&lt;br /&gt;
 	/* Draw animated health bar inside */&lt;br /&gt;
 	float const s = sin(0.001f * timer_ms_gettime64());&lt;br /&gt;
 	uint8_t health_count = 15 + 70 * s*s;&lt;br /&gt;
 	draw_sprite(&amp;amp;ui_sheet, &amp;quot;healthbar_left&amp;quot;, 16, 18, 0);&lt;br /&gt;
 	for(int x = 0; x &amp;lt; health_count; ++x)&lt;br /&gt;
 		draw_sprite(&amp;amp;ui_sheet, &amp;quot;healthbar_mid&amp;quot;, 16+5 + x, 18, 0);&lt;br /&gt;
 	draw_sprite(&amp;amp;ui_sheet, &amp;quot;healthbar_right&amp;quot;,   16+5 + health_count, 18, 0);&lt;br /&gt;
&lt;br /&gt;
 	/* Draw animated enemies */&lt;br /&gt;
 	uint8_t sprite_number = (unsigned)(0.01f * timer_ms_gettime64()) % 8;&lt;br /&gt;
 	char sprite_name[32];&lt;br /&gt;
 	snprintf(sprite_name, sizeof(sprite_name), &amp;quot;mage_combat%u&amp;quot;, sprite_number);&lt;br /&gt;
 	sprite_name[31] = 0;&lt;br /&gt;
 	draw_sprite(&amp;amp;stage1_actors_sheet, sprite_name, 100, 300, stage1_actors_palette_number);&lt;br /&gt;
 &lt;br /&gt;
 	snprintf(sprite_name, sizeof(sprite_name), &amp;quot;mage_shadowform%u&amp;quot;, sprite_number);&lt;br /&gt;
 	sprite_name[31] = 0;&lt;br /&gt;
 	draw_sprite(&amp;amp;stage1_actors_sheet, sprite_name, 250, 300, stage1_actors_palette_number);&lt;br /&gt;
 &lt;br /&gt;
 	snprintf(sprite_name, sizeof(sprite_name), &amp;quot;mage_idle%u&amp;quot;, sprite_number);&lt;br /&gt;
 	sprite_name[31] = 0;&lt;br /&gt;
 	draw_sprite(&amp;amp;stage1_actors_sheet, sprite_name, 350, 300, stage1_actors_palette_number);&lt;br /&gt;
&lt;br /&gt;
After this the scene is drawn, so the PVR needs to be told to present the result:&lt;br /&gt;
 	pvr_scene_finish();&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Spritesheet Loader ===&lt;br /&gt;
The loading code can be found in&lt;br /&gt;
 int spritesheet_load(&lt;br /&gt;
 	struct spritesheet * const spritesheet, /* Where to store the loaded information */&lt;br /&gt;
 	char const * const image_filename,      /* foo.dtex     */&lt;br /&gt;
 	char const * const palette_filename,    /* foo.dtex.pal */&lt;br /&gt;
 	char const * const sheet_filename);     /* foo.txt      */&lt;br /&gt;
&lt;br /&gt;
This function reads the &amp;#039;&amp;#039;foo.dtex&amp;#039;&amp;#039; texture and loads it into video memory. To allocate video memory you should use the KOS function &amp;#039;&amp;#039;pvr_mem_malloc()&amp;#039;&amp;#039;. It will also load the corresponding palette file into memory. The DTEX and DPAL file formats are documented in texconv&amp;#039;s readme.&lt;br /&gt;
&lt;br /&gt;
Lastly this function parses the sprite geometry information from the accompanying &amp;#039;&amp;#039;.txt&amp;#039;&amp;#039; file that TexturePacker created.&lt;br /&gt;
The amount of lines in the file tells us the amount of sprites in the spritesheet.&lt;br /&gt;
&lt;br /&gt;
It is parsed in the following way:&lt;br /&gt;
 	struct sprite * sprite = sprites;&lt;br /&gt;
 	for(uint16_t i = 0; i &amp;lt; sprite_count &amp;amp;&amp;amp; !feof(sheet_file); ++i) {&lt;br /&gt;
 		int scanned = fscanf(sheet_file, &amp;quot;%[^,], %hu, %hu, %hu, %hu, 0, 0, 0, 0\n&amp;quot;, sprite-&amp;gt;name, &amp;amp;sprite-&amp;gt;x, &amp;amp;sprite-&amp;gt;y, &amp;amp;sprite-&amp;gt;width, &amp;amp;sprite-&amp;gt;height);&lt;br /&gt;
 		printf(&amp;quot;scanned %d parameters: %s %d %d %d %d\n&amp;quot;, scanned, sprite-&amp;gt;name, sprite-&amp;gt;x, sprite-&amp;gt;y, sprite-&amp;gt;width, sprite-&amp;gt;height);&lt;br /&gt;
 		if(scanned != 5) {&lt;br /&gt;
 			result = 10;&lt;br /&gt;
 			goto cleanup;&lt;br /&gt;
 		}&lt;br /&gt;
 		++sprite;&lt;br /&gt;
 	}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;%[^,]&amp;#039;&amp;#039; is a quite underused format in &amp;#039;&amp;#039;scanf&amp;#039;&amp;#039;. It means &amp;#039;&amp;#039;a string without ,&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
So all this does is read a name that can be used in &amp;#039;&amp;#039;draw_sprite()&amp;#039;&amp;#039;, as well as x and y offset and width and height of the sprite.&lt;br /&gt;
&lt;br /&gt;
=== Speciyfing the Palette ===&lt;br /&gt;
This is done in &amp;#039;&amp;#039;setup_palette()&amp;#039;&amp;#039; and is very simple. Just keep in mind that multiple textures&amp;#039; palettes share the global palette memory. So some housekeeping needs to be performed to avoid overwriting palette memory that&amp;#039;s used by other textures. You can also give a texture a different palette to create some amazing [http://www.effectgames.com/demos/canvascycle palette animations].&lt;br /&gt;
&lt;br /&gt;
We leave the first 256 entries to what we&amp;#039;ll call palette 0 and the next 256 to palette 1.&lt;br /&gt;
&lt;br /&gt;
 void setup_palette(uint32_t const * colors, uint16_t count, uint8_t palette_number) {&lt;br /&gt;
 	pvr_set_pal_format(PVR_PAL_ARGB8888);&lt;br /&gt;
 	for(uint16_t i = 0; i &amp;lt; count; ++i)&lt;br /&gt;
 		pvr_set_pal_entry(i + &amp;#039;&amp;#039;&amp;#039;256 * palette_number&amp;#039;&amp;#039;&amp;#039;, colors[i]);&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Drawing sprites ===&lt;br /&gt;
This is done in &amp;#039;&amp;#039;draw_sprite()&amp;#039;&amp;#039; and is used as seen before in &amp;#039;&amp;#039;draw()&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
 void draw_sprite(struct spritesheet const * const sheet, char const * const name, float x, float y, uint8_t palette_number) {&lt;br /&gt;
&lt;br /&gt;
Remember that the palette must have been set up before, so this function is told which part of the global palette memory is being used.&lt;br /&gt;
&lt;br /&gt;
First we look up the information about the particular sprite called &amp;#039;&amp;#039;name&amp;#039;&amp;#039; we are tasked to draw. You might want to do this using a hashmap, I did a simple array walk.&lt;br /&gt;
 	/* Find sprite by name */&lt;br /&gt;
 	uint16_t const sprite_count = sheet-&amp;gt;sprite_count;&lt;br /&gt;
 	struct sprite const * const sprites = sheet-&amp;gt;sprites;&lt;br /&gt;
 	struct sprite const * sprite = NULL;&lt;br /&gt;
 	for(uint16_t i = 0; i &amp;lt; sprite_count; ++i) {&lt;br /&gt;
 		if(!strcmp(sprites[i].name, name)) {&lt;br /&gt;
 			sprite = &amp;amp;sprites[i];&lt;br /&gt;
 			break;&lt;br /&gt;
 		}&lt;br /&gt;
 	}&lt;br /&gt;
 &lt;br /&gt;
 	if(sprite == NULL) {&lt;br /&gt;
 		printf(&amp;quot;There is no sprite named &amp;#039;%s&amp;#039; in this spritesheet.\n&amp;quot;, name);&lt;br /&gt;
 		return;&lt;br /&gt;
 	}&lt;br /&gt;
&lt;br /&gt;
This gives us a pointer to a struct of the following form:&lt;br /&gt;
 struct sprite {&lt;br /&gt;
 	char name[32];&lt;br /&gt;
 	uint16_t x, y, width, height;&lt;br /&gt;
 };&lt;br /&gt;
&lt;br /&gt;
We&amp;#039;ll be using this information to calculate the proper UV-coordinates. The whole texture of the entire spritesheet has coordinates on the horizontal axis in the range U=[0.0, 1.0] and the vertical axis V=[0.0, 1.0]. Therefore we can determine the UV-coordinates of this particular tile by the following calculation:&lt;br /&gt;
 	float x0 = x; /* function arguments: where to draw the sprite on screen */&lt;br /&gt;
 	float y0 = y;&lt;br /&gt;
 	float x1 = x + sprite-&amp;gt;width;&lt;br /&gt;
 	float y1 = y + sprite-&amp;gt;height;&lt;br /&gt;
 	float z = 1;&lt;br /&gt;
 &lt;br /&gt;
 	float u0 =  sprite-&amp;gt;x                   / (float)sheet-&amp;gt;width; /* Relative coordinates, [0.0,1.0] range */&lt;br /&gt;
 	float v0 =  sprite-&amp;gt;y                   / (float)sheet-&amp;gt;height;&lt;br /&gt;
 	float u1 = (sprite-&amp;gt;x + sprite-&amp;gt;width)  / (float)sheet-&amp;gt;width;&lt;br /&gt;
 	float v1 = (sprite-&amp;gt;y + sprite-&amp;gt;height) / (float)sheet-&amp;gt;height;&lt;br /&gt;
&lt;br /&gt;
As a little extra, I will not use polygons to draw the sprites. We&amp;#039;ll be using the Dreamcast&amp;#039;s special &amp;#039;&amp;#039;sprite drawing mode&amp;#039;&amp;#039;.&lt;br /&gt;
In sprite drawing mode, you specify the minimum and maximum values of the rectangle and its texture coordinates, instead of specifying each point. This works because sprites cannot be rotated. These sprites also cannot have a color. This makes them super efficient (for bullet hell games etc.), but you will need to use 3D polygons for 3D effects.&lt;br /&gt;
&lt;br /&gt;
Define which palette, texture and polyon list are to be used (punchthru for alpha masking here). Filtering is disabled because sprites are displayed 1:1.&lt;br /&gt;
 	pvr_sprite_cxt_t sprite_context; /* This is just a convenience function for creating the following. */&lt;br /&gt;
 	pvr_sprite_hdr_t sprite_header;  /* This is sent to the PVR before geometry is sent. */&lt;br /&gt;
 	pvr_sprite_cxt_txr(&amp;amp;sprite_context, PVR_LIST_PT_POLY, PVR_TXRFMT_PAL8BPP | PVR_TXRFMT_8BPP_PAL(palette_number), sheet-&amp;gt;width, sheet-&amp;gt;height, sheet-&amp;gt;texture, PVR_FILTER_NONE);&lt;br /&gt;
 	pvr_sprite_compile(&amp;amp;sprite_header, &amp;amp;sprite_context);&lt;br /&gt;
 &lt;br /&gt;
 	pvr_prim(&amp;amp;sprite_header, sizeof(sprite_header)); /* Send header to PVR */&lt;br /&gt;
 &lt;br /&gt;
 	pvr_sprite_txr_t vert = {&lt;br /&gt;
 		.flags = PVR_CMD_VERTEX_EOL,&lt;br /&gt;
 		.ax = x0, .ay = y0, .az = z,&lt;br /&gt;
  		.bx = x1, .by = y0, .bz = z,&lt;br /&gt;
  		.cx = x1, .cy = y1, .cz = z,&lt;br /&gt;
  		.dx = x0, .dy = y1,&lt;br /&gt;
 		.auv = PVR_PACK_16BIT_UV(u0, v0),&lt;br /&gt;
 		.buv = PVR_PACK_16BIT_UV(u1, v0),&lt;br /&gt;
 		.cuv = PVR_PACK_16BIT_UV(u1, v1),&lt;br /&gt;
 	};&lt;br /&gt;
 	pvr_prim(&amp;amp;vert, sizeof(vert));&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
As you see it took only 2 calls to &amp;#039;&amp;#039;pvr_prim()&amp;#039;&amp;#039; to send a sprite. That said, &amp;#039;&amp;#039;pvr_prim()&amp;#039;&amp;#039; is not an efficient way of sending geometry to the PVR graphics chip.&lt;br /&gt;
&lt;br /&gt;
If you want to make a bullet hell game with a serious amount of sprites, you should look into &amp;#039;&amp;#039;store queues&amp;#039;&amp;#039; to transfer whole arrays of sprites. The header only needs to be set once before drawing sprites too.&lt;br /&gt;
&lt;br /&gt;
==== PVR Sprite Draw Mode Caveat ====&lt;br /&gt;
While the &amp;#039;&amp;#039;triangle strip drawing mode&amp;#039;&amp;#039; of the PVR uses 32 bit floating point values for the texture coordinates, the &amp;#039;&amp;#039;sprite draw mode&amp;#039;&amp;#039; requires you to use 16 bit floating point values. If you don&amp;#039;t position your sprites carefully, you will therefore see the following problem (note the distortion in the right character&amp;#039;s animation):&lt;br /&gt;
&lt;br /&gt;
[[file:spritesheet_tutorial_demo_sprite.gif]]&lt;br /&gt;
&lt;br /&gt;
To fix this you will either need to position the sprites so that 16 bit floats can represent the texture coordinate properly, or you can use polygon drawing mode instead. I implemented this in the tutorial as well and polygon drawing mode is used by default.&lt;/div&gt;</summary>
		<author><name>Unknown user</name></author>
	</entry>
</feed>