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
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(&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>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 <sys/types.h> // This provides typedefs needed by libgte.h and libgpu.h
|
|
#include <stdio.h> // Not necessary but include it anyway
|
|
#include <libetc.h> // Includes some functions that controls the display
|
|
#include <libgte.h> // GTE header, not really used but libgpu.h depends on it
|
|
#include <libgpu.h> // 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(&disp;[db]); // Apply the DISPENV/DRAWENVs
|
|
PutDrawEnv(&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(&disp;[0], 0, 0, 320, 240);
|
|
SetDefDrawEnv(&draw;[0], 0, 240, 320, 240);
|
|
// Second buffer
|
|
SetDefDispEnv(&disp;[1], 0, 240, 320, 240);
|
|
SetDefDrawEnv(&draw;[1], 0, 0, 320, 240);
|
|
|
|
draw[0].isbg = 1; // Enable clear
|
|
setRGB0(&draw;[0], 63, 0, 127); // Set clear color (dark purple)
|
|
draw[1].isbg = 1;
|
|
setRGB0(&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
|
|
-->
|