Tools for preprocessing data files from Quake to make them suitable for use on PS1 hardware
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

413 lines
20 KiB

<html><head><script src="Chapter%201.2%20Drawing%20Graphics_files/analytics.js" type="text/javascript"></script>
<script type="text/javascript">window.addEventListener('DOMContentLoaded',function(){var v=archive_analytics.values;v.service='wb';v.server_name='wwwb-app228.us.archive.org';v.server_ms=369;archive_analytics.send_pageview({});});</script>
<script type="text/javascript" src="Chapter%201.2%20Drawing%20Graphics_files/bundle-playback.js" charset="utf-8"></script>
<script type="text/javascript" src="Chapter%201.2%20Drawing%20Graphics_files/wombat.js" charset="utf-8"></script>
<script type="text/javascript">
__wm.init("http://web.archive.org/web");
__wm.wombat("http://lameguy64.net/tutorials/pstutorials/chapter1/2-graphics.html","20220827033534","http://web.archive.org/","web","/_static/",
"1661571334");
</script>
<link rel="stylesheet" type="text/css" href="Chapter%201.2%20Drawing%20Graphics_files/banner-styles.css">
<link rel="stylesheet" type="text/css" href="Chapter%201.2%20Drawing%20Graphics_files/iconochive.css">
<!-- End Wayback Rewrite JS Include -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="Chapter%201.2%20Drawing%20Graphics_files/style.css">
<title>Chapter 1.2: Drawing Graphics</title><meta http-equiv="Content-Type" content="text/html; charset=windows-1252"></head>
<body><!-- BEGIN WAYBACK TOOLBAR INSERT -->
<style type="text/css">
body {
margin-top:0 !important;
padding-top:0 !important;
/*min-width:800px !important;*/
}
</style>
<script>__wm.rw(0);</script>
<div id="wm-ipp-base" style="display: block; direction: ltr; height: 1px;" lang="en">
</div><div id="wm-ipp-print">The Wayback Machine -
http://web.archive.org/web/20220827033534/http://lameguy64.net/tutorials/pstutorials/chapter1/2-graphics.html</div>
<script type="text/javascript">//<![CDATA[
__wm.bt(675,27,25,2,"web","http://lameguy64.net/tutorials/pstutorials/chapter1/2-graphics.html","20220827033534",1996,"/_static/",["/_static/css/banner-styles.css?v=fantwOh2","/_static/css/iconochive.css?v=qtvMKcIJ"], false);
__wm.rw(1);
//]]></script>
<!-- END WAYBACK TOOLBAR INSERT -->
<h1>1.2. Drawing Graphics</h1>
<p>This tutorial will teach you how to draw graphics primitives with the GPU
using an ordering table and primitive packets. This is a very essential part
to learn about the PS1 as you need graphics to do anything on the console
really.</p>
<p>Just like in the last tutorial, libgs will not be covered here. Also, the
GPU is not responsible for 3D graphics. While it does render out (affine)
polygon all the 3D processing is actually done in a co-processor, called
the GTE or Geometry Transformation Engine.</p>
<p><b><i>Trivia:</i></b> The easiest way to tell if a person is not a PS1 programmer
is if they call the GTE the Geometry <i>Transfer</i> Engine instead of
<b>Transformation</b> Engine. The GTE does NOT transfer anything, it is
essentially like a math co-processor on the CPU as nothing but the CPU has
access to it, and it does nothing else but vector transformations that helps
greatly in 3D graphics processing.</p>
<p><b>Tutorial compatible with PSn00bSDK:</b>Yes</p>
<h2>Tutorial Index</h2>
<ul>
<li><a href="#operation">Theory of Operation</a></li>
<li><a href="#ordertable">Ordering Tables</a></li>
<li><a href="#primitives">Primitive Packets</a></li>
<li><a href="#primprep">Preparing a Primitive</a></li>
<li><a href="#primbuff">Primitive Buffers</a></li>
<li><a href="#otsort">Sorting a Primitive to an Ordering Table</a></li>
<li><a href="#otdraw">Drawing an Ordering Table</a></li>
<li><a href="#doublebuff">Double Ordering Tables/Primitive Buffers</a></li>
<li><a href="#samplecode">Sample Code</a></li>
<li><a href="#conclusion">Conclusion</a></li>
</ul>
<h2 id="operation">Theory of Operation</h2>
<p>The PS1 GPU draws graphics by means of primitive packets, which are
essentially messages that command the GPU to draw a specified primitive
in a specified color to specified screen coordinates to name a few.</p>
<p>Drawing packets are normally sent to the GPU by an ordering table,
which is essentially an array of pointers that form a chain. Primitive
packets are normally linked to the ordering table to draw them.</p>
<h2 id="ordertable">Ordering Tables</h2>
<p>An ordering table is an array of elements of pointers that point from
one element to another, the ordering table usually ends with an array element
with the value of 0xFFFFFFFF which is used as a terminator value for ordering
table processing. An ordering table is normally created using <b>ClearOTagR()</b>
on an array of 32-bit ints.</p>
<p>The following figure visually describes the general structure of an ordering table.</p>
<img src="Chapter%201.2%20Drawing%20Graphics_files/ordertable.svg">
<p>Adding primitives to an ordering table is normally achieved using
<b>addPrim()/AddPrim()</b> functions. <b>DrawOTag()</b> is used to begin
processing of an ordering table.</p>
<p>The type of ordering table shown above is called a reverse ordering table
because the chain starts at the end of the array and ends at the beginning of
the array. This type of ordering table is most commonly used as the reverse
order allows for depth ordering of polygons which is essential for 3D graphics.
Use <b>ClearOTag()</b> if you wish to use a non-reverse ordering table but the
initialization speed won't be as fast as <b>ClearOTagR()</b> for larger tables
as <b>ClearOTagR()</b> is DMA accelerated, but it can only generate reverse
ordering tables.</p>
<p>For simplicity, a global ordering table array of 8 elements should suffice
for this tutorial.</p>
<pre>int ot[2][8];
</pre>
<p>The reason two arrays of <h>ot</h> are defined is for double buffered
rendering reasons, which will be explained later in this tutorial.</p>
<h2 id="primitives">Primitive Packets</h2>
<p>Primitive packets are more or less commands that instruct the GPU to draw
a specified primitive to specified coordinates of the drawing area carried by
the packet. Primitive packets are always aligned 4-byte aligned.</p>
<p>The following illustrates the typical structure of a primitive packet
(the packet represents a <b>SPRT</b> primitive):</p>
<img src="Chapter%201.2%20Drawing%20Graphics_files/primstruct.svg">
<p>C implementation of the above structure</p>
<pre>typedef struct {
unsigned int tag; // First 24 bits are address, last 8 bits are length
unsigned char r0,g0,b0,code; // RGB color and primitive code
short x0,y0; // X,Y coordinates of sprite
unsigned char u0,v0; // U,V texture coordinates of sprite
unsigned short clut; // Texture CLUT
unsigned short w,h; // Sprite width and height
} SPRT;
</pre>
<p>The number of parameter words of a primitive packet varies on the primitive
command. Simpler commands such as a fixed sized, solid color sprite is only 3
words long whereas a shaded, textured 4-point polygon is 13 words long, the
length of the primitive in words is specified to the <i>Len</i> field minus the
tag word. The <i>Next Pointer</i> field is an address to the next primitive or
return pointer to an ordering table it was sorted to.</p>
<p>A word in this context is a 4 byte integer.</p>
<p>Primitives are normally defined using primitive structures and macros
defined in libgpu.h, or psxgpu.h in PSn00bSDK.</p>
<h2 id="primprep">Preparing a Primitive</h2>
<p>Primitive packets are normally prepared using primitive structures and
macros. A solid color rectangular sprite (<b>TILE</b>) will be used for this
tutorial.</p>
<p>Primitive preparation is as follows.</p>
<pre>TILE tile; // Primitive structure
setTile(&amp;tile;); // Initialize the primitive (very important)
setXY0(&amp;tile;, 32, 32); // Set primitive (x,y) position
setWH(&amp;tile;, 64, 64); // Set primitive size
setRGB0(&amp;tile;, 255, 255, 0); // Set color yellow
</pre>
<p>The <b>setTile()</b> macro simply fills in the appropriate values to the tag
length and primitive code fields to the specified primitive. These values are
mandatory and must be correct, otherwise the GPU will lock up. Always use the
appropriate initializer macro for a primitive.</p>
<p>The <b>setXY0()</b>, <b>setWH()</b> and <b>setRGB0()</b> macros sets the
(x,y) coordinates, size and color of the primitive respectively. While the
fields of a primitive can be set directly via struct elements of the
primitive, using macros helps make the code looking tidy.</p>
<p>Normally, primitive packets are defined to a primitive buffer.</p>
<h2 id="primbuff">Primitive Buffers</h2>
<p>A primitive buffer is simply a global array of char elements used as a
buffer to define primitive packets to. It is also a lot less wasteful than
defining multiple arrays for different primitive types as all primitive types
can be defined in just a single global buffer.</p>
<p>The reason it is recommended that primitive packets must be defined in a
global buffer is because the primitives must exist in memory until the GPU
gets around to processing it. If you were to define primitives as a local
variable in a function and register it to an ordering table, that primitive
has most likely been overwritten by other things (since locals are generally
temporary variables), resulting in a corrupted ordering table which would
result to a GPU lock up or crash.</p>
<p>A primitive buffer can be defined as such, 32KB buffer for primitives
should be plenty.</p>
<pre>char primbuff[2][32768];
char *nextpri;
</pre>
<p>The <h>nextpri</h> variable will be used to keep track where the next
primitive should be written to. Therefore, this variable must be set to
the first primitive buffer and reset in your display function.</p>
<p>To prepare a primitive to the primitive buffer, simply cast the
nextpri pointer into a primitive pointer.</p>
<pre>TILE *tile; // Primitive pointer
tile = (TILE*)nextpri; // Cast hext primitive
setTile(tile); // Initialize the primitive (very important)
setXY0(tile, 32, 32); // Set primitive (x,y) position
setWH(tile, 64, 64); // Set primitive size
setRGB0(tile, 255, 255, 0); // Set color yellow
</pre>
<p>After registering the primitive to the ordering table, you'll want to
advance the <i>nextpri</i> pointer for another primitive to be written to.</p>
<pre>nextpri += sizeof(TILE);
</pre>
<p>It is very important to advance this pointer, otherwise previously defined
packets would get overwritten, corrupting the primitive buffer.</p>
<h2 id="otsort">Sorting a Primitive to an Ordering Table</h2>
<p>The term 'sorting' in the context of PS1 graphics programming refers to
linking a primitive to an ordering table element. Its often called sorting
because an ordering table is also used to control the order of which
primitives are drawn. In a reverse ordering table (initialized using
ClearOTagR()) a primitive sorted highest will be drawn first and primitives
sorted lowest will be drawn last, which would make a lot of sense in 3D
graphics.</p>
<p>Sorting a primitive to an ordering table is achieved using the
<b>addPrim()</b> macro.</p>
<pre>addPrim(ot[db], tile);
</pre>
<p>There's also a function version called <b>AddPrim()</b> but the macro version
would be faster in the long run, mainly because code jumps are kept a minimum when
using macros.</p>
<p>Taking the code snippet earlier, this is the process of how to create and sort
a primitive to be drawn.</p>
<pre>tile = (TILE*)nextpri; // Cast next primitive
setTile(tile); // Initialize the primitive (very important)
setXY0(tile, 32, 32); // Set primitive (x,y) position
setWH(tile, 64, 64); // Set primitive size
setRGB0(tile, 255, 255, 0); // Set color yellow
addPrim(ot[db], tile); // Add primitive to the ordering table
nextpri += sizeof(TILE); // Advance the next primitive pointer
</pre>
<p>Primitives need to be sorted to an ordering table for the GPU to process it.
To sort the primitive to a higher ordering table element, simply use (+) to
increment the ordering table address. But don't increment by a value that
will exceed the length of your ordering table, or that will result to memory
overflow corruption.</p>
<p>Sorting a primitive to an ordering table links the specified primitive into
the chain, so it gets processed when the OT is proceessed by the GPU. The
following figure visually describes the result of sorting a primitive to ordering
table element 4.</p>
<img src="Chapter%201.2%20Drawing%20Graphics_files/ordertable-primitive.svg">
<p>A very common misconception among many PS1 homebrew programmers is they believe
only a single primitive can be sorted to an ordering table element. This is
absolutely untrue because sorting a primitive to an element that has a primitive
already linked to it will only add further to the chain, not replace the previous
element.</p>
<img src="Chapter%201.2%20Drawing%20Graphics_files/ordertable-multiprims.svg">
<p>You can sort any number of primitives to a single ordering table, so an ordering
table of 4 to 8 elements should be sufficient for 2D projects. But remember that the
newest primitive sorted will be the first to be processed, as shown in the figure
earlier.</p>
<h2 id="otdraw">Drawing an Ordering Table</h2>
<p>Ordering table processing is done using the <b>DrawOTag()</b> function.
<b>DrawOTag()</b> starts a DMA transfer process of the specified ordering
table for the GPU to process. This is the most recommended method of
processing ordering tables as this is the fastest way to deliver graphics
primitives to the GPU.</p>
<p>Since this tutorial demonstrates the use of a reverse ordering table, the
last element of the ordering table must be specified to <b>DrawOTag()</b>, as
the chain starts at the last element and ends at the first.</p>
<pre>DrawOTag(ot[db]+31);
</pre>
<p>The transfer operation is asynchronous so the function returns very
quickly. It is recommended to wait for the transfer and the GPU to finish
processing which is achieved using <b>DrawSync()</b> before processing another
ordering table.</p>
<h2 id="doublebuff">Double Ordering Tables/Primitive Buffers</h2>
<p>You may have noticed by now that ordering tables and primitive buffers are
defined in pairs. This is so that a commonly used optimization trick can be
achieved and is another form of double buffering. Because the CPU can continue
running while the GPU is busy processing graphics, you can utilize this to
continue processing graphics primitives for drawing the next frame while the
GPU is drawing. The following figure visually describes the performance
difference.</p>
<img src="Chapter%201.2%20Drawing%20Graphics_files/ordertable-buffercompare.svg">
<p>Having double buffers essentially minimizes keeping the GPU idle which
comes off as being most efficient, as having single buffers would prevent
you from preparing primitives for the next frame which results to leaving
the GPU in an idle state. While this may not be apparent in small programs,
it will be once you start pushing the GPU with thousands of primitives.</p>
<h2 id="samplecode">Sample Code</h2>
<p>The sample code works off of the one from the previous example, but with
graphics drawing code implemented.</p>
<pre>#include &lt;sys/types.h&gt; // This provides typedefs needed by libgte.h and libgpu.h
#include &lt;stdio.h&gt; // Not necessary but include it anyway
#include &lt;libetc.h&gt; // Includes some functions that controls the display
#include &lt;libgte.h&gt; // GTE header, not really used but libgpu.h depends on it
#include &lt;libgpu.h&gt; // GPU library header
#define OTLEN 8 // Ordering table length (recommended to set as a define
// so it can be changed easily)
DISPENV disp[2];
DRAWENV draw[2];
int db = 0;
u_long ot[2][OTLEN]; // Ordering table length
char pribuff[2][32768]; // Primitive buffer
char *nextpri; // Next primitive pointer
void display() {
DrawSync(0); // Wait for any graphics processing to finish
VSync(0); // Wait for vertical retrace
PutDispEnv(&amp;disp;[db]); // Apply the DISPENV/DRAWENVs
PutDrawEnv(&amp;draw;[db]);
SetDispMask(1); // Enable the display
DrawOTag(ot[db]+OTLEN-1); // Draw the ordering table
db = !db; // Swap buffers on every pass (alternates between 1 and 0)
nextpri = pribuff[db]; // Reset next primitive pointer
}
int main() {
TILE *tile; // Primitive pointer
// Reset graphics
ResetGraph(0);
// First buffer
SetDefDispEnv(&amp;disp;[0], 0, 0, 320, 240);
SetDefDrawEnv(&amp;draw;[0], 0, 240, 320, 240);
// Second buffer
SetDefDispEnv(&amp;disp;[1], 0, 240, 320, 240);
SetDefDrawEnv(&amp;draw;[1], 0, 0, 320, 240);
draw[0].isbg = 1; // Enable clear
setRGB0(&amp;draw;[0], 63, 0, 127); // Set clear color (dark purple)
draw[1].isbg = 1;
setRGB0(&amp;draw;[1], 63, 0, 127);
nextpri = pribuff[0]; // Set initial primitive pointer address
while(1) {
ClearOTagR(ot[db], OTLEN); // Clear ordering table
tile = (TILE*)nextpri; // Cast next primitive
setTile(tile); // Initialize the primitive (very important)
setXY0(tile, 32, 32); // Set primitive (x,y) position
setWH(tile, 64, 64); // Set primitive size
setRGB0(tile, 255, 255, 0); // Set color yellow
addPrim(ot[db], tile); // Add primitive to the ordering table
nextpri += sizeof(TILE); // Advance the next primitive pointer
// Update the display
display();
}
return 0;
}
</pre>
<p>Compile and run the program and you should get a yellow square.
Your very first graphic done with the GPU!</p>
<img src="Chapter%201.2%20Drawing%20Graphics_files/2-graphics.png">
<h2 id="conclusion">Conclusion</h2>
<p>This concludes Chapter 1.2. of Lameguy64's PSX Tutorial series.
You should know more about drawing graphics on the PS1 after reading
through this chapter.</p>
<p>A few things you may want to experiment with yourself for further
learning:</p>
<ul>
<li>Play around with the values specified in <b>setXY0()</b>, <b>setRGB0()</b>
and <b>setWH()</b> to change the position, color and size of the sprite
respectively.</li>
<li>Try drawing more sprites by repeating the primitive creation process.
Make sure the <h>nextpri</h> and <h>tile</h> pointers have been advanced before
creating a new primitive.</li>
<li>You can advance the primitive pointer with <h>tile++;</h>, and set the
updated address to <h>nextpri</h> by converting types back
(<h>nextpri = (char*)tile;</h>)</li>
<li>Try making the yellow square bounce around the screen, by defining
two variables for (x,y) coordinates and two more for the direction flags,
and write some logic that makes the (x,y) coordinates move and bounce
around the screen.</li>
</ul>
<p>The next tutorial will cover how to convert and upload texture data, as
well as drawing said textures with sprite and polygon primitives.</p>
<hr>
<table class="footer-table" width="100%">
<tbody><tr>
<td width="40%" align="left"><a href="http://web.archive.org/web/20220827033534/http://lameguy64.net/tutorials/pstutorials/chapter1/1-display.html">Previous</a></td>
<td width="20%" align="center"><a href="http://web.archive.org/web/20220827033534/http://lameguy64.net/tutorials/pstutorials/index.html">Back to Index</a></td>
<td width="40%" align="right"><a href="http://web.archive.org/web/20220827033534/http://lameguy64.net/tutorials/pstutorials/chapter1/3-textures.html">Next</a></td>
</tr>
</tbody></table>
</body></html>
<!--
FILE ARCHIVED ON 03:35:34 Aug 27, 2022 AND RETRIEVED FROM THE
INTERNET ARCHIVE ON 15:40:29 Sep 05, 2022.
JAVASCRIPT APPENDED BY WAYBACK MACHINE, COPYRIGHT INTERNET ARCHIVE.
ALL OTHER CONTENT MAY ALSO BE PROTECTED BY COPYRIGHT (17 U.S.C.
SECTION 108(a)(3)).
-->
<!--
playback timings (ms):
captures_list: 241.637
exclusion.robots: 0.473
exclusion.robots.policy: 0.464
RedisCDXSource: 14.466
esindex: 0.01
LoadShardBlock: 195.528 (3)
PetaboxLoader3.datanode: 171.89 (4)
CDXLines.iter: 27.223 (3)
load_resource: 118.452
PetaboxLoader3.resolve: 85.381
-->