diff --git a/Docs/Chapter 1.1 Setting up Graphics and Hello World.htm b/Docs/Chapter 1.1 Setting up Graphics and Hello World.htm
new file mode 100644
index 0000000..8c865ad
--- /dev/null
+++ b/Docs/Chapter 1.1 Setting up Graphics and Hello World.htm
@@ -0,0 +1,796 @@
+
+
+
+
+
+
+
+
+
+
+
+Chapter 1.1: Setting up Graphics and Hello World
+
+
+
+
+
+
+
The Wayback Machine -
+http://web.archive.org/web/20220827033533/http://lameguy64.net/tutorials/pstutorials/chapter1/1-display.html
+
+
+
+
+
1.1. Setting up Graphics and Hello World
+
+This chapter teaches the basics of initializing and setting up the GPU of the
+PSX. Areas covered in this chapter include the GPU, VRAM and the concept of
+display and drawing environments which are essential for better understanding
+how the GPU works when programming for the PSX.
+
+
+This tutorial series does not and will not cover graphics programming using the
+LIBGS library which is a high level graphics library that adds about 100KB
+worth of overhead to your project (remember that the PSX only has 2MB of RAM)
+and hides many of the inner workings of the GPU, making it difficult to
+understand how to use the GPU directly and would often get in the way when
+trying to write your own graphics routines... While it may sound elitist
+discouraging the use of LIBGS as it provides facilities for rendering 3D models
+easily, it is better to learn how to work with LIBGPU (or PSXGPU in PSn00bSDK)
+in the long term as the knowledge gained from learning how to use it directly
+will become very important in the future for either better performance, or to
+accomplish special graphical effects.
+
+This tutorial series begins with graphics programming as displaying a hello
+world message requires a bit of graphics programming, as neither the official
+SDK or PSn00bSDK provide a framebuffer text console for displaying text largely
+due to the way how the hardware works. But both provide a means of basic text
+drawing functions intended for debugging purposes which will be demonstrated
+for this chapter.
+
+
+Besides, most of the satisfaction gained when learning to develop for a new
+platform comes from seeing the result displayed on the television screen by the
+console you're learning to develop for and you're going need to learn graphics
+programming anyway to really be able to do anything interesting with the PSX.
+It is a video game console afterall.
+
+
+
+
Brief Summary of the GPU
+
+The GPU, as the name suggests, is the graphics processor responsible for doing
+the graphics rendering work for both 2D and 3D visuals. The block diagram shown
+below illustrates the relevant components that make up the entirerity of the
+PSX's graphics subsystem, including the MDEC and GTE hardware responsible for
+video playback and 3D graphics capabilities respectively.
+
+
+
+
1MB of VRAM with 16-bit data bus.
+
2KB of texture cache.
+
Supports non-interlaced, low resolution and interlaced, high resolution
+video modes.
+
16-bit color rendering with optional dithering.
+
Draws rectangles, sprites, lines, polylines, flat, shaded and texture
+mapped polygon primitives.
+
Estimated 300,000 flat and gouraud shaded polygons per second.
+
Estimated 150,000 texture mapped polygons per second.
+
+
+
The GPU is not 3D
+
+A worrying amount often believed that the PSX's GPU is capable of rendering 3D
+polygons. While it may seem to be the case to the uneducated, the GPU is in
+fact only capable of drawing primitives in 2D space. Therefore, the GPU is
+incapable of perspective correct texture mapping which results in affine
+texture distortion and the occasional buggy near clipping seen in a lot of 3D
+PSX games. It was common for games to minimize the distortion by subdividing
+large polygons into smaller polygons with perspective correct subdivision.
+
+
+The bulk of the 3D processing is instead done on the CPU which features a
+co-processor suited for such tasks known as the GTE or Geometry Transform
+Engine. The GTE is effectively an all integer math co-processor that provides
+matrix multiplication, vector transformations and projection operations at high
+speed which the CPU alone couldn't do at reasonable speed. Software would then
+assemble primitive packets using results from the GTE which are then sent to
+the GPU as a linked list via high speed DMA transfer for the GPU to draw said
+primitives.
+
+
+The GTE will not be covered in this chapter as it is not required for simple
+2D graphics programming. Whilst in the topic of the GTE, a very important
+issue must be taken care of and that is the GTE is NOT a Geometry
+Transfer Engine. An equally concerning amount belive that the GTE
+stands for Geometry Transfer Engine and not a Transform Engine when in fact,
+the GTE only does math calculations and is not capable of doing any
+"transfer" operations whatsoever. Also, the GTE can only be accessed
+by the CPU through standard co-processor related instructions. Just wanted to
+clarify this before going any further in learning how to program for the PSX
+as this bothers me to no end. Suppose it can be used to identify if one is a
+real PSX programmer or not.
+
+
+
Only 16-bit Color Rendering
+
+Some often mistake that the PSX renders graphics in 24-bit color depth. Whilst
+the GPU is capable of 24-bit true color output, it cannot draw primitives in
+24-bit color. So the 24-bit color mode is only really useful for MDEC video
+sequences or static images in 24-bit color.
+
+
+The GPU can only draw at 16-bit color depth in a RGB5I1 pixel format (five bits
+per RGB component plus one mask bit used as a mask for semi-transparency
+effects). This aligns perfectly with the 16-bit data bus of the VRAM.
+
+
+
The VRAM
+
+The GPU features it's own 1MB of VRAM. If you look back at the block diagram
+shown earlier you'll see that the VRAM is connected to the GPU and is not
+connected the system bus at all. This means the VRAM is not memory mapped and
+the CPU cannot access it directly. Instead, VRAM access can only be done
+through the GPU. Fortunately, transfers are done via high speed DMA.
+
+
+Naturally, the GPU uses the VRAM for storing framebuffers and texture images.
+The most interesting aspect about the VRAM is how the GPU treats it like a
+1024x512 16-bit image, with areas of VRAM addressed by (X,Y) coordinates
+instead of a linear memory address range. Display and drawing framebuffers as
+well as texture images reside as rectangular images in the VRAM. The following
+illustrates a typical VRAM layout for framebuffers and texture images in PSX
+games.
+
+
+
+
Primitive Packets
+
+The GPU is instructed through the use of primitive packets, which are small
+bits of formatted data that make up a GPU command which are sent to the GPU
+via DMA for drawing. The primitive packets not only specify the type of
+primitive but also the color, semi-transparency flags, (X,Y) coordinates,
+texture coordinates, color look-up table to use and so forth. Think of them
+like arguments for a function call.
+
+
+The number of arguments a primitive packet requires depends on the type of
+primitive. The simplest primitives such as a texture page primitive only takes
+one argument while the most sophisticated primitive; a texture mapped, gouraud
+shaded, 4-point polygon takes as many as 14 arguments. This also means the
+size of the primitive varies, with the texture page primitive being only a
+word in size while a 4-point, texture mapped and gouraud shaded polygon is
+12 words long on top of the tag word present at the start of all primitives.
+
+
+Further details about primitive packets will be covered in a future chapter.
+
+
+
Framebuffers
+
+Framebuffers for graphics display and drawing reside in VRAM as rectangular
+areas defined in (X,Y) coordinates. The size of the display area is based on
+the video mode (ie. the display area is 320x240 pixels when running in
+320x240 mode). On the other hand, the size for the drawing area can be of any
+arbitrary size which may be used to render texture images for accomplishing
+certain render to texture based visual effects. Normally, the size of the
+drawing area should typically be equal to the display area.
+
+
+The display and drawing areas are defined as an environment using the
+DISPENV and DRAWENV structs. Each struct defines various
+parameters that are relevant to their respective environment. The
+DISPENV and DRAWENV structs are initialized using
+SetDefDispEnv() and SetDefDrawEnv() respectively which defines
+the default parameters for each environment, which may be customized later for
+your project's requirements. Once the environments have been defined they can
+then be applied to the GPU using PutDispEnv() and PutDrawEnv()
+to make each environment effective.
+
+
+Learning the display and drawing environments are mandatory for being able to
+get any graphical output from the PSX and this will be covered extensively in
+this chapter.
+
+
+
Writing the Hello World Program
+
+As obligatory as it may sound, a hello world is an effective and simple enough
+example to get started with PSX homebrew programming. A hello world program for
+the PSX can't be a simple call of printf(), unless you only want to see the
+output through a tty which, in all honesty, won't look as interesting as
+showing the message on the TV screen or emulator window.
+
+
+The basic run-down of accomplishing a hello world on the PSX is to perform the
+following operations:
+
+
+
Initialize the GPU.
+
Define display and drawing environments.
+
Initializing the debug font.
+
Display the hello world text.
+
+
+Break out your preferred text editor and begin writing the hello world program
+as you go along this chapter.
+
+
+
Required Headers
+
+First, include some necessary headers in your C file. The most important of which is
+libgpu.h as it provides various definitions provided by the libgpu library. libgte.h
+is required even if you don't plan to use the GTE as libgpu.h depends on some
+definitions provided in it.
+
+
#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
+
+
+If using PSn00bSDK, replace the lib prefix with psx instead. Most
+of the definitions between the official SDK and PSn00bSDK are identical, so the
+syntax of things shouldn't be much to worry much about.
+
+
+
Write the main() Function
+
+Like with any C program, you must define a main() function somewhere in your
+project. You can omit the argc and argv definitions if desired as they don't
+work on the PSX.
+
+
int main()
+{
+ return 0;
+}
+
+
+A return value cannot be passed to a parent executable as the kernel does not
+save the value for the caller to receive it, but is required nonetheless to
+keep the compiler from complaining about returning without a value.
+
+
+
+
Setting up Graphics
+
+The very first thing to do before anything else in your PSX program is to
+call ResetGraph(). ResetGraph() not only resets the GPU as the
+name suggests, but it also enables interrupts which are mandatory for getting
+anything done on the PSX. If ResetGraph() is not called functions that
+depend on interrupts such as VSync() and several other things will not
+work, and will usually appear as though your program just crashed.
+
+
// Reset GPU and enable interrupts
+ResetGraph(0);
+
+
+Resetting the GPU also masks out the video output which results in a black
+screen, but the console still produces video sync. The video output is enabled
+again using SetDispMask(), ideally after a DISPENV has been
+defined and applied to the GPU for a smooth transition from a previously
+running program (ie. the startup screen). As ResetGraph() does not clear
+the contents of VRAM, so whatever was drawn by the previous program still
+remains when your program takes over. Clearing the VRAM is generally not
+required in most well coded scenarios.
+
+
+
Setting up the DISPENV and DRAWENV Environments
+
+Start by defining two DISPENV and DRAWENV variables as arrays in
+your code. Define an int which will be used for keeping track of things which
+will make total sense later on. This value must be set to zero as part of your
+graphics initialization code just to make sure the variable starts with a value
+of zero.
+
+Next is to define the DISPENV pair. Initializing the DISPENV
+structure is easiest done using the SetDefDispEnv() function with simple
+arguments.
+
+For NTSC users:
+
// Configures the pair of DISPENVs for 320x240 mode (NTSC)
+SetDefDispEnv(&disp;[0], 0, 0, 320, 240);
+SetDefDispEnv(&disp;[1], 0, 240, 320, 240);
+
+For PAL users:
+
// Configures the pair of DISPENVs for 320x256 mode (PAL)
+SetDefDispEnv(&disp;[0], 0, 0, 320, 256);
+SetDefDispEnv(&disp;[1], 0, 256, 320, 256);
+
+// Screen offset to center the picture vertically
+disp[0].screen.y = 24;
+disp[1].screen.y = disp[0].screen.y;
+
+// Forces PAL video standard
+SetVideoMode(MODE_PAL);
+
+
+This defines both DISPENVs for 320x240 resolution mode (320x256 if you
+used the PAL snippet) which is the most commonly used video mode. The first
+DISPENV is set with a position of (0,0) while the second DISPENV
+is set with a VRAM offset of (0,240). You might be wondering why it has to be
+defined that way.
+
+
+Next is to define the DRAWENVs. Much like the DISPENVs you use
+SetDefDrawEnv() to initialize the DRAWENV structure with
+simple arguments.
+
+For NTSC users:
+
// Configures the pair of DRAWENVs for the DISPENVs
+SetDefDrawEnv(&draw;[0], 0, 240, 320, 240);
+SetDefDrawEnv(&draw;[1], 0, 0, 320, 240);
+
+For PAL users:
+
// Configures the pair of DRAWENVs for the DISPENVs
+SetDefDrawEnv(&draw;[0], 0, 256, 320, 256);
+SetDefDrawEnv(&draw;[1], 0, 0, 320, 256);
+
+
+Unlike DISPENV, DRAWENV can be of any arbitrary size for the
+drawing environment. But generally it should be equal to the size of the
+DISPENV defined, so the drawn area will align perfectly with the display
+environments. Additionally, you'll want to set some parameters within the
+DRAWENV struct to enable background clearing as otherwise you'll get a
+hall of mirrors effect. The background clear takes effect as soon as the
+DRAWENV is applied to the GPU using PutDrawEnv().
+
+
// Specifies the clear color of the DRAWENV
+setRGB0(&draw;[0], 63, 0, 127);
+setRGB0(&draw;[1], 63, 0, 127);
+// Enable background clear
+draw[0].isbg = 1;
+draw[1].isbg = 1;
+
+
+Finally, apply the DISPENV/DRAWENV environments to the GPU to
+apply the new video mode and drawing environment to achieve a seamless
+transition.
+
+To keep your code look neat and tidy, you'll want to put all the graphics init
+code inside a function, to keep your main() function from looking messy
+and will make expanding the program easier in the future.
+
+
+
The Concept of Double Buffered Rendering
+
+You may have noticed that the DRAWENV pairs are somewhat positioned in
+the opposite order as the DISPENV whilst writing the graphics init code
+and are probably wondering why this is the case instead of a DRAWENV
+directly overlapping a DISPENV. This is how you implement double
+buffered rendering on the PSX and is a standard feature to have when writing
+real-time graphics code, not just on the PSX but on almost any system with a
+framebuffer.
+
+
+If you're not familiar with the concept, you basically need to allocate two
+framebuffer areas; one for display and one for drawing. The way you use these
+framebuffers is you use one buffer as the display buffer, which is the buffer
+the graphics hardware will display on the screen and the other as the drawing
+buffer, where all your graphics operations should draw to and is not shown on
+the screen. Once drawing is complete the two buffers switch places, the drawing
+buffer becomes the display buffer and the display buffer become the drawing
+buffer for the next frame to be drawn and the cycle repeats. This basically
+guarantees that only completed frames are shown on the screen which yields
+seamless graphical animations even during framerate drops when intensive
+visuals are being drawn aside from the natural reduction of smoothness.
+
+
+
+Having a DISPENV and a DRAWENV simply overlap one another on the
+same area counts as single buffered rendering and whilst you may get away with
+it, the amount of things you can draw/process will be severely limited as you'll
+get nasty flicker if the drawing/processing does not complete before the v-blank
+period ends. So, a double buffered rendering scheme is much preferred.
+
+
+
The Display Function
+
+Now the last thing that deals with the DISPENV and DRAWENV
+environments to write is a so called display function, which is basically
+a function that does the all buffer swap stuff in a single call for
+convenience. Calling the display function should be done at the end of your
+loop as that is usually where all graphics operations have completed
+and are ready for drawing or display. For this tutorial, the display function
+will be named display().
+
+
+Before performing a buffer swap, you must first call DrawSync() then
+VSync(). As the names suggests these functions waits for the GPU to
+complete any drawing operations and waits for the vertical blanking period
+respectively. Waiting for DrawSync() is important as it makes sure that
+the GPU has completed drawing any primitives as otherwise you may get flicker
+or possibly instability. Waiting for VSync() is also important as it
+not only caps your program loop to the TV refresh rate (60fps for NTSC, 50
+for PAL) but it also prevents screen tearing which will happen if you swap
+buffers without waiting for the v-blank period.
+
+
// Wait for GPU to finish drawing and V-Blank
+DrawSync(0);
+VSync(0);
+
+
+Now the next step is to perform the buffer swap. If you remember that variable
+named db earlier, this variable will be used as a counter to keep track
+of which buffer pair to switch to. Since there are only two DISPENV and
+DRAWENV pairs this variable will simply alternate between 1 and 0 on
+every call of the display function. This easily achieved using a NOT (!)
+operator.
+
+
// Flip buffer counter
+db = !db;
+
+
+Then apply the environment pair based on the value of db to the GPU.
+
+And finally, call SetDispMask() to lift the display mask so you get
+picture instead of a black screen, as ResetGraph() masks the display
+by default.
+
+
// Enable display
+SetDispMask(1);
+
+
+
Verifying the Code
+
+To make sure you're right on track, here's a listing of what the code should
+look like.
+
+
#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 environment pairs and buffer counter
+DISPENV disp[2];
+DRAWENV draw[2];
+int db;
+
+void init(void)
+{
+ // Reset GPU and enable interrupts
+ ResetGraph(0);
+
+ // Configures the pair of DISPENVs for 320x240 mode (NTSC)
+ SetDefDispEnv(&disp;[0], 0, 0, 320, 240);
+ SetDefDispEnv(&disp;[1], 0, 240, 320, 240);
+
+ // Configures the pair of DRAWENVs for the DISPENVs
+ SetDefDrawEnv(&draw;[0], 0, 240, 320, 240);
+ SetDefDrawEnv(&draw;[1], 0, 0, 320, 240);
+
+ // Specifies the clear color of the DRAWENV
+ setRGB0(&draw;[0], 63, 0, 127);
+ setRGB0(&draw;[1], 63, 0, 127);
+
+ // Enable background clear
+ draw[0].isbg = 1;
+ draw[1].isbg = 1;
+
+ // Apply environments
+ PutDispEnv(&disp;[0]);
+ PutDrawEnv(&draw;[0]);
+
+ // Make sure db starts with zero
+ db = 0;
+}
+
+void display(void)
+{
+ // Wait for GPU to finish drawing and V-Blank
+ DrawSync(0);
+ VSync(0);
+
+ // Flip buffer counter
+ db = !db;
+
+ // Apply environments
+ PutDispEnv(&disp;[db]);
+ PutDrawEnv(&draw;[db]);
+
+ // Enable display
+ SetDispMask(1);
+}
+
+int main()
+{
+ // Initialize graphics and stuff
+ init();
+
+ // Main loop
+ while(1)
+ {
+ display();
+ }
+
+ return 0;
+}
+
+
+
Compiling and Testing
+
Now that you've written a considerable amount of code for the graphics
+init and buffer swapping stuff, it should be possible to compile the code
+and see an output to check if you're right on track. In the official
+PsyQ/Programmers' Tool SDK compiling can be done through ccpsx with
+a single command line. ccpsx is actually a front for the C, C++ and
+linker that makes using the SDK's toolchain easier.
+
+
ccpsx -O2 -Xo0x80010000 hello.c -o hello.cpe
+
+
+If you've worked with C before, you should already know that -O2
+specifies the optimization level for the compiler generated code just like
+in most other C compilers. The -Xo0x80010000 parameter would likely
+be unfamiliar to you and this specifies the target address your executable's
+program text is going to reside when it is being loaded by the console.
+The target address is usually set to 0x80010000 which is the start of user
+space memory on the PSX as the first 64KB is reserved by the kernel.
+
+
+The executables ccpsx produces are in CPE format which cannot be
+booted from the CD by the PSX and most emulators don't support it. You'll
+have to convert the executable into the usable PS-EXE format which can be
+done using cpe2x.
+
+
cpe2x hello.cpe
+
+
Don't be fooled by the .EXE file extension of a PS-EXE executable file. The
+executable files cpe2x produces are neither MS-DOS or Windows
+executable programs.
+
+
If using PSn00bSDK. It is more complicated to compile a program from within
+the command line alone due to the way how the toolchain is currently set up.
+It is much easier to just copy a makefile from one of the examples included
+in the SDK and tweak the variables for your project. Then use make
+to compile the program and it should produce an ELF file and a PS-EXE file.
+
+
Run the program and you should get a solid blue image.
+
+
+
+
+
Displaying the Hello World
+
Now that the graphics environment and display routines have been implemented,
+the next step is to draw the hello world message. This is easily done by using
+the debug font functions FntLoad(), FntOpen() and
+FntPrint(). These functions are not suited for general use. Such as in
+a game title as it does not support any form of customization and does not
+support lowercase letters, but is good enough for testing and debugging
+purposes which will be useful later. These functions are provided by libetc,
+or psxetc in PSn00bSDK.
+
+
+
Setting up the Debug Font
+
The first thing to do before the debug font functions can be used is to is
+load the font texture onto VRAM using FntLoad(). The font texture can be
+placed anywhere in the VRAM area provided it doesn't get overwritten by
+display/draw areas, or textures being loaded onto VRAM. For this tutorial, the
+font texture will be placed at (960,0).
+
+
Next is to create a text stream using FntOpen() which defines the
+area where the text will be drawn at. For this tutorial a text stream of 100
+characters is defined to fill the entire screen, with some overscan
+compensation to ensure that the text is visible on the TV.
+
+
// Load the internal font texture
+FntLoad(960, 0);
+// Create the text stream
+FntOpen(0, 8, 320, 224, 0, 100);
+
+
The font area coordinates are draw-area relative and not VRAM relative, so
+there is no need to define another font area for each of the display/draw areas
+in the VRAM. The third argument specifies if a black background for the font
+area should be drawn. In PSn00bSDK, you can specify 2 for a semi-transparent
+black background. If you try to set it to a non-zero value, the background will
+turn black because the font area covers the entire screen.
+
+
FntOpen() allows defining multiple font areas by saving the return
+value to a persistent variable and passing it as the first argument of
+FntPrint() and FntFlush(). Up to eight font areas can be defined
+at once, but it cannot be closed or redefined later in your program.
+
+
Printing and Displaying Hello World
+
FntPrint() prints text to the last defined font area and works more
+or less like printf() and you can also use the newline character
+sequence \n to move a line of text down. A font handle can be specified
+as an optional first argument of FntPrint(), but in PSn00bSDK a handle
+from FntOpen() or -1 must be specified as the first argument. This
+
Calling FntPrint() alone is not enough to display text to the
+current draw area, so FntFlush() needs to be called to actually draw
+the characters printed from FntPrint() calls.
+
+
+
Final Sample Code
+
Compare your code against this listing to make sure you're right on
+track.
+
+
#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 environment pairs and buffer counter
+DISPENV disp[2];
+DRAWENV draw[2];
+int db;
+
+void init(void)
+{
+ // Reset GPU and enable interrupts
+ ResetGraph(0);
+
+ // Configures the pair of DISPENVs for 320x240 mode (NTSC)
+ SetDefDispEnv(&disp;[0], 0, 0, 320, 240);
+ SetDefDispEnv(&disp;[1], 0, 240, 320, 240);
+
+ // Configures the pair of DRAWENVs for the DISPENVs
+ SetDefDrawEnv(&draw;[0], 0, 240, 320, 240);
+ SetDefDrawEnv(&draw;[1], 0, 0, 320, 240);
+
+ // Specifies the clear color of the DRAWENV
+ setRGB0(&draw;[0], 63, 0, 127);
+ setRGB0(&draw;[1], 63, 0, 127);
+
+ // Enable background clear
+ draw[0].isbg = 1;
+ draw[1].isbg = 1;
+
+ // Apply environments
+ PutDispEnv(&disp;[0]);
+ PutDrawEnv(&draw;[0]);
+
+ // Make sure db starts with zero
+ db = 0;
+
+ // Load the internal font texture
+ FntLoad(960, 0);
+ // Create the text stream
+ FntOpen(0, 8, 320, 224, 0, 100);
+}
+
+void display(void)
+{
+ // Wait for GPU to finish drawing and V-Blank
+ DrawSync(0);
+ VSync(0);
+
+ // Flip buffer counter
+ db = !db;
+
+ // Apply environments
+ PutDispEnv(&disp;[db]);
+ PutDrawEnv(&draw;[db]);
+
+ // Enable display
+ SetDispMask(1);
+}
+
+int main()
+{
+ // Initialize graphics and stuff
+ init();
+
+ // Main loop
+ while(1)
+ {
+ FntPrint("HELLO WORLD!");
+
+ FntFlush(-1);
+ display();
+ }
+
+ return 0;
+}
+
+
+
Result
+
Run the program on the console or emulator and you should see a
+dark purple picture:
+
+
+
Conclusion
+
This concludes this chapter of Lameguy64's PSX Tutorial series. You should
+be very familiar of how framebuffers are handled on the PSX hardware and
+should be ready to take on drawing proper graphics primitives.
+
A few things you may want to experiment with this example yourself:
+
+
Try ordering the areas side by side.
+
Go to the VRAM/GPU Viewer in no$psx or press F12 in PSXfin to visually
+see the buffers in action. This also works for retail games.
+
Play around with the clear color values on the DRAWENV environments.
+Make sure the color values on both DRAWENV elements are the same
+otherwise intense flickering will occur.
+
+
The next tutorial will cover how to draw graphics with the GPU using
+ordering tables and primitive packets.
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/analytics.js b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/analytics.js
new file mode 100644
index 0000000..10400f0
--- /dev/null
+++ b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/analytics.js
@@ -0,0 +1,474 @@
+// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3.0
+/* eslint-disable no-var, semi, prefer-arrow-callback, prefer-template */
+
+/**
+ * Collection of methods for sending analytics events to Archive.org's analytics server.
+ *
+ * These events are used for internal stats and sent (in anonymized form) to Google Analytics.
+ *
+ * @see analytics.md
+ *
+ * @type {Object}
+ */
+window.archive_analytics = (function defineArchiveAnalytics() {
+ // keep orignal Date object so as not to be affected by wayback's
+ // hijacking global Date object
+ var Date = window.Date;
+ var ARCHIVE_ANALYTICS_VERSION = 2;
+ var DEFAULT_SERVICE = 'ao_2';
+ var NO_SAMPLING_SERVICE = 'ao_no_sampling'; // sends every event instead of a percentage
+
+ var startTime = new Date();
+
+ /**
+ * @return {Boolean}
+ */
+ function isPerformanceTimingApiSupported() {
+ return 'performance' in window && 'timing' in window.performance;
+ }
+
+ /**
+ * Determines how many milliseconds elapsed between the browser starting to parse the DOM and
+ * the current time.
+ *
+ * Uses the Performance API or a fallback value if it's not available.
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance_API
+ *
+ * @return {Number}
+ */
+ function getLoadTime() {
+ var start;
+
+ if (isPerformanceTimingApiSupported())
+ start = window.performance.timing.domLoading;
+ else
+ start = startTime.getTime();
+
+ return new Date().getTime() - start;
+ }
+
+ /**
+ * Determines how many milliseconds elapsed between the user navigating to the page and
+ * the current time.
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance_API
+ *
+ * @return {Number|null} null if the browser doesn't support the Performance API
+ */
+ function getNavToDoneTime() {
+ if (!isPerformanceTimingApiSupported())
+ return null;
+
+ return new Date().getTime() - window.performance.timing.navigationStart;
+ }
+
+ /**
+ * Performs an arithmetic calculation on a string with a number and unit, while maintaining
+ * the unit.
+ *
+ * @param {String} original value to modify, with a unit
+ * @param {Function} doOperation accepts one Number parameter, returns a Number
+ * @returns {String}
+ */
+ function computeWithUnit(original, doOperation) {
+ var number = parseFloat(original, 10);
+ var unit = original.replace(/(\d*\.\d+)|\d+/, '');
+
+ return doOperation(number) + unit;
+ }
+
+ /**
+ * Computes the default font size of the browser.
+ *
+ * @returns {String|null} computed font-size with units (typically pixels), null if it cannot be computed
+ */
+ function getDefaultFontSize() {
+ var fontSizeStr;
+
+ if (!('getComputedStyle' in window))
+ return null;
+
+ var style = window.getComputedStyle(document.documentElement);
+ if (!style)
+ return null;
+
+ fontSizeStr = style.fontSize;
+
+ // Don't modify the value if tracking book reader.
+ if (document.querySelector('#BookReader'))
+ return fontSizeStr;
+
+ return computeWithUnit(fontSizeStr, function reverseBootstrapFontSize(number) {
+ // Undo the 62.5% size applied in the Bootstrap CSS.
+ return number * 1.6;
+ });
+ }
+
+ /**
+ * Get the URL parameters for a given Location
+ * @param {Location}
+ * @return {Object} The URL parameters
+ */
+ function getParams(location) {
+ if (!location) location = window.location;
+ var vars;
+ var i;
+ var pair;
+ var params = {};
+ var query = location.search;
+ if (!query) return params;
+ vars = query.substring(1).split('&');
+ for (i = 0; i < vars.length; i++) {
+ pair = vars[i].split('=');
+ params[pair[0]] = decodeURIComponent(pair[1]);
+ }
+ return params;
+ }
+
+ function getMetaProp(name) {
+ var metaTag = document.querySelector('meta[property=' + name + ']');
+ return metaTag ? metaTag.getAttribute('content') || null : null;
+ }
+
+ var ArchiveAnalytics = {
+ /**
+ * @type {String|null}
+ */
+ service: getMetaProp('service'),
+ mediaType: getMetaProp('mediatype'),
+ primaryCollection: getMetaProp('primary_collection'),
+
+ /**
+ * Key-value pairs to send in pageviews (you can read this after a pageview to see what was
+ * sent).
+ *
+ * @type {Object}
+ */
+ values: {},
+
+ /**
+ * Sends an analytics ping, preferably using navigator.sendBeacon()
+ * @param {Object} values
+ * @param {Function} [onload_callback] (deprecated) callback to invoke once ping to analytics server is done
+ * @param {Boolean} [augment_for_ao_site] (deprecated) if true, add some archive.org site-specific values
+ */
+ send_ping: function send_ping(values, onload_callback, augment_for_ao_site) {
+ if (typeof window.navigator !== 'undefined' && typeof window.navigator.sendBeacon !== 'undefined')
+ this.send_ping_via_beacon(values);
+ else
+ this.send_ping_via_image(values);
+ },
+
+ /**
+ * Sends a ping via Beacon API
+ * NOTE: Assumes window.navigator.sendBeacon exists
+ * @param {Object} values Tracking parameters to pass
+ */
+ send_ping_via_beacon: function send_ping_via_beacon(values) {
+ var url = this.generate_tracking_url(values || {});
+ window.navigator.sendBeacon(url);
+ },
+
+ /**
+ * Sends a ping via Image object
+ * @param {Object} values Tracking parameters to pass
+ */
+ send_ping_via_image: function send_ping_via_image(values) {
+ var url = this.generate_tracking_url(values || {});
+ var loadtime_img = new Image(1, 1);
+ loadtime_img.src = url;
+ loadtime_img.alt = '';
+ },
+
+ /**
+ * Construct complete tracking URL containing payload
+ * @param {Object} params Tracking parameters to pass
+ * @return {String} URL to use for tracking call
+ */
+ generate_tracking_url: function generate_tracking_url(params) {
+ var baseUrl = '//analytics.archive.org/0.gif';
+ var keys;
+ var outputParams = params;
+ var outputParamsArray = [];
+
+ outputParams.service = outputParams.service || this.service || DEFAULT_SERVICE;
+
+ // Build array of querystring parameters
+ keys = Object.keys(outputParams);
+ keys.forEach(function keyIteration(key) {
+ outputParamsArray.push(encodeURIComponent(key) + '=' + encodeURIComponent(outputParams[key]));
+ });
+ outputParamsArray.push('version=' + ARCHIVE_ANALYTICS_VERSION);
+ outputParamsArray.push('count=' + (keys.length + 2)); // Include `version` and `count` in count
+
+ return baseUrl + '?' + outputParamsArray.join('&');
+ },
+
+ /**
+ * @param {int} page Page number
+ */
+ send_scroll_fetch_event: function send_scroll_fetch_event(page) {
+ var additionalValues = { ev: page };
+ var loadTime = getLoadTime();
+ var navToDoneTime = getNavToDoneTime();
+ if (loadTime) additionalValues.loadtime = loadTime;
+ if (navToDoneTime) additionalValues.nav_to_done_ms = navToDoneTime;
+ this.send_event('page_action', 'scroll_fetch', location.pathname, additionalValues);
+ },
+
+ send_scroll_fetch_base_event: function send_scroll_fetch_base_event() {
+ var additionalValues = {};
+ var loadTime = getLoadTime();
+ var navToDoneTime = getNavToDoneTime();
+ if (loadTime) additionalValues.loadtime = loadTime;
+ if (navToDoneTime) additionalValues.nav_to_done_ms = navToDoneTime;
+ this.send_event('page_action', 'scroll_fetch_base', location.pathname, additionalValues);
+ },
+
+ /**
+ * @param {Object} [options]
+ * @param {String} [options.mediaType]
+ * @param {String} [options.mediaLanguage]
+ * @param {String} [options.page] The path portion of the page URL
+ */
+ send_pageview: function send_pageview(options) {
+ var settings = options || {};
+
+ var defaultFontSize;
+ var loadTime = getLoadTime();
+ var mediaType = settings.mediaType;
+ var primaryCollection = settings.primaryCollection;
+ var page = settings.page;
+ var navToDoneTime = getNavToDoneTime();
+
+ /**
+ * @return {String}
+ */
+ function get_locale() {
+ if (navigator) {
+ if (navigator.language)
+ return navigator.language;
+
+ else if (navigator.browserLanguage)
+ return navigator.browserLanguage;
+
+ else if (navigator.systemLanguage)
+ return navigator.systemLanguage;
+
+ else if (navigator.userLanguage)
+ return navigator.userLanguage;
+ }
+ return '';
+ }
+
+ defaultFontSize = getDefaultFontSize();
+
+ // Set field values
+ this.values.kind = 'pageview';
+ this.values.timediff = (new Date().getTimezoneOffset()/60)*(-1); // *timezone* diff from UTC
+ this.values.locale = get_locale();
+ this.values.referrer = (document.referrer == '' ? '-' : document.referrer);
+
+ if (loadTime)
+ this.values.loadtime = loadTime;
+
+ if (navToDoneTime)
+ this.values.nav_to_done_ms = navToDoneTime;
+
+ if (settings.trackingId) {
+ this.values.ga_tid = settings.trackingId;
+ }
+
+ /* START CUSTOM DIMENSIONS */
+ if (defaultFontSize)
+ this.values.ga_cd1 = defaultFontSize;
+
+ if ('devicePixelRatio' in window)
+ this.values.ga_cd2 = window.devicePixelRatio;
+
+ if (mediaType)
+ this.values.ga_cd3 = mediaType;
+
+ if (settings.mediaLanguage) {
+ this.values.ga_cd4 = settings.mediaLanguage;
+ }
+
+ if (primaryCollection) {
+ this.values.ga_cd5 = primaryCollection;
+ }
+ /* END CUSTOM DIMENSIONS */
+
+ if (page)
+ this.values.page = page;
+
+ this.send_ping(this.values);
+ },
+
+ /**
+ * Sends a tracking "Event".
+ * @param {string} category
+ * @param {string} action
+ * @param {string} label
+ * @param {Object} additionalEventParams
+ */
+ send_event: function send_event(
+ category,
+ action,
+ label,
+ additionalEventParams
+ ) {
+ if (!label) label = window.location.pathname;
+ if (!additionalEventParams) additionalEventParams = {};
+ if (additionalEventParams.mediaLanguage) {
+ additionalEventParams.ga_cd4 = additionalEventParams.mediaLanguage;
+ delete additionalEventParams.mediaLanguage;
+ }
+ var eventParams = Object.assign(
+ {
+ kind: 'event',
+ ec: category,
+ ea: action,
+ el: label,
+ cache_bust: Math.random(),
+ },
+ additionalEventParams
+ );
+ this.send_ping(eventParams);
+ },
+
+ /**
+ * Sends every event instead of a small percentage.
+ *
+ * Use this sparingly as it can generate a lot of events.
+ *
+ * @param {string} category
+ * @param {string} action
+ * @param {string} label
+ * @param {Object} additionalEventParams
+ */
+ send_event_no_sampling: function send_event_no_sampling(
+ category,
+ action,
+ label,
+ additionalEventParams
+ ) {
+ var extraParams = additionalEventParams || {};
+ extraParams.service = NO_SAMPLING_SERVICE;
+ this.send_event(category, action, label, extraParams);
+ },
+
+ /**
+ * @param {Object} options see this.send_pageview options
+ */
+ send_pageview_on_load: function send_pageview_on_load(options) {
+ var self = this;
+ window.addEventListener('load', function send_pageview_with_options() {
+ self.send_pageview(options);
+ });
+ },
+
+ /**
+ * Handles tracking events passed in URL.
+ * Assumes category and action values are separated by a "|" character.
+ * NOTE: Uses the unsampled analytics property. Watch out for future high click links!
+ * @param {Location}
+ */
+ process_url_events: function process_url_events(location) {
+ var eventValues;
+ var actionValue;
+ var eventValue = getParams(location).iax;
+ if (!eventValue) return;
+ eventValues = eventValue.split('|');
+ actionValue = eventValues.length >= 1 ? eventValues[1] : '';
+ this.send_event_no_sampling(
+ eventValues[0],
+ actionValue,
+ window.location.pathname
+ );
+ },
+
+ /**
+ * Attaches handlers for event tracking.
+ *
+ * To enable click tracking for a link, add a `data-event-click-tracking`
+ * attribute containing the Google Analytics Event Category and Action, separated
+ * by a vertical pipe (|).
+ * e.g. ``
+ *
+ * To enable form submit tracking, add a `data-event-form-tracking` attribute
+ * to the `form` tag.
+ * e.g. `",f=l.a(i,"%d %b %Y");s!=i&&(f+=" - "+l.a(s,"%d %b %Y")),h+='
'+f+"
",t.innerHTML=h}(o),function(e,t,n,o,i,r,s){var a=o.getContext("2d");if(a){a.fillStyle="#FFF";var c=(new u).getUTCFullYear(),l=t/(c-i+1),h=f(e.years),p=h[0],d=n/h[1];if(r>=i){var m=T(r);a.fillStyle="#FFFFA5",a.fillRect(m,0,l,n)}for(var v=i;v<=c;v++){m=T(v);a.beginPath(),a.moveTo(m,0),a.lineTo(m,n),a.lineWidth=1,a.strokeStyle="#CCC",a.stroke()}s=parseInt(s)-1;for(var g=(l-1)/12,y=0;y0){var M=Math.ceil(S*d);a.fillStyle=v==r&&_==s?"#EC008C":"#000",a.fillRect(Math.round(b),Math.ceil(n-M),Math.ceil(g),Math.round(M))}b+=g}}}function T(e){return Math.ceil((e-i)*l)+.5}}(o,e,t,Z,a,_,S)}}))}else{var te=new Image;te.src="/__wb/sparkline?url="+encodeURIComponent(r)+"&width="+e+"&height="+t+"&selected_year="+_+"&selected_month="+S+(i&&"&collection="+i||""),te.alt="sparkline",te.width=e,te.height=t,te.id="sparklineImgId",te.border="0",Q.parentNode.replaceChild(te,Q)}function ne(e){for(var t=[],n=e.length,o=0;o0){var o=ne(n.hosts);t(o)}else void 0!==n.isUrl&&!0===n.isUrl&&void 0===n.excluded?t([e]):Object(c.a)("GET","/__wb/search/anchor?q="+encodeURIComponent(e),(function(e){if(void 0!==(e=A.parse(e.response))&&e.length>0){var n=ne(e.slice(0,5));t(n)}}))}))},onSelect:function(e,t,n){P("wmtb").submit()}}),P("wmtb").onsubmit=function(e){var t=P("wmtbURL").value;if(0!==t.indexOf("http://")&&0!==t.indexOf("https://")&&!t.match(/[\w\.]{2,256}\.[a-z]{2,4}/gi))return document.location.href="/web/*/"+P("wmtbURL").value,e.preventDefault(),!1},function(e,t){$(e,t,(function(e,t){e?(P("wm-screenshot").title="screen shot (delta: "+t+"s)",X("wm-screenshot",!0)):X("wm-screenshot",!1)}))}(r,q),m&&function(e,t){!function(e,t,n){var o="/web/"+t+"id_/http://wayback-metadata.archive.org/youtube-dl/"+e;Object(c.a)("GET",o,n)}(e,t,(function(e){if(e.status<300){var t=A.parse(e.responseText);X("wm-video",!0),P("wm-video").href=t.url,P("wm-video").title="Video: "+t.title}else X("wm-video",!1)}))}(r,q),P("wm-capinfo-notice")&&B(!0),new R.a(P("wm-save-snapshot-open"),r,q)},h:void 0,ex:function(e){e.stopPropagation(),B(!1)},ajax:c.a}}]);
+// @license-end
diff --git a/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/gpudiagram.svg b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/gpudiagram.svg
new file mode 100644
index 0000000..a694157
--- /dev/null
+++ b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/gpudiagram.svg
@@ -0,0 +1,286 @@
+
+
+
+
diff --git a/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/iconochive.css b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/iconochive.css
new file mode 100644
index 0000000..7a95ea7
--- /dev/null
+++ b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/iconochive.css
@@ -0,0 +1,116 @@
+@font-face{font-family:'Iconochive-Regular';src:url('https://archive.org/includes/fonts/Iconochive-Regular.eot?-ccsheb');src:url('https://archive.org/includes/fonts/Iconochive-Regular.eot?#iefix-ccsheb') format('embedded-opentype'),url('https://archive.org/includes/fonts/Iconochive-Regular.woff?-ccsheb') format('woff'),url('https://archive.org/includes/fonts/Iconochive-Regular.ttf?-ccsheb') format('truetype'),url('https://archive.org/includes/fonts/Iconochive-Regular.svg?-ccsheb#Iconochive-Regular') format('svg');font-weight:normal;font-style:normal}
+[class^="iconochive-"],[class*=" iconochive-"]{font-family:'Iconochive-Regular'!important;speak:none;font-style:normal;font-weight:normal;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}
+.iconochive-Uplevel:before{content:"\21b5"}
+.iconochive-exit:before{content:"\1f6a3"}
+.iconochive-beta:before{content:"\3b2"}
+.iconochive-logo:before{content:"\1f3db"}
+.iconochive-audio:before{content:"\1f568"}
+.iconochive-movies:before{content:"\1f39e"}
+.iconochive-software:before{content:"\1f4be"}
+.iconochive-texts:before{content:"\1f56e"}
+.iconochive-etree:before{content:"\1f3a4"}
+.iconochive-image:before{content:"\1f5bc"}
+.iconochive-web:before{content:"\1f5d4"}
+.iconochive-collection:before{content:"\2211"}
+.iconochive-folder:before{content:"\1f4c2"}
+.iconochive-data:before{content:"\1f5c3"}
+.iconochive-tv:before{content:"\1f4fa"}
+.iconochive-article:before{content:"\1f5cf"}
+.iconochive-question:before{content:"\2370"}
+.iconochive-question-dark:before{content:"\3f"}
+.iconochive-info:before{content:"\69"}
+.iconochive-info-small:before{content:"\24d8"}
+.iconochive-comment:before{content:"\1f5e9"}
+.iconochive-comments:before{content:"\1f5ea"}
+.iconochive-person:before{content:"\1f464"}
+.iconochive-people:before{content:"\1f465"}
+.iconochive-eye:before{content:"\1f441"}
+.iconochive-rss:before{content:"\221e"}
+.iconochive-time:before{content:"\1f551"}
+.iconochive-quote:before{content:"\275d"}
+.iconochive-disc:before{content:"\1f4bf"}
+.iconochive-tv-commercial:before{content:"\1f4b0"}
+.iconochive-search:before{content:"\1f50d"}
+.iconochive-search-star:before{content:"\273d"}
+.iconochive-tiles:before{content:"\229e"}
+.iconochive-list:before{content:"\21f6"}
+.iconochive-list-bulleted:before{content:"\2317"}
+.iconochive-latest:before{content:"\2208"}
+.iconochive-left:before{content:"\2c2"}
+.iconochive-right:before{content:"\2c3"}
+.iconochive-left-solid:before{content:"\25c2"}
+.iconochive-right-solid:before{content:"\25b8"}
+.iconochive-up-solid:before{content:"\25b4"}
+.iconochive-down-solid:before{content:"\25be"}
+.iconochive-dot:before{content:"\23e4"}
+.iconochive-dots:before{content:"\25a6"}
+.iconochive-columns:before{content:"\25af"}
+.iconochive-sort:before{content:"\21d5"}
+.iconochive-atoz:before{content:"\1f524"}
+.iconochive-ztoa:before{content:"\1f525"}
+.iconochive-upload:before{content:"\1f4e4"}
+.iconochive-download:before{content:"\1f4e5"}
+.iconochive-favorite:before{content:"\2605"}
+.iconochive-heart:before{content:"\2665"}
+.iconochive-play:before{content:"\25b6"}
+.iconochive-play-framed:before{content:"\1f3ac"}
+.iconochive-fullscreen:before{content:"\26f6"}
+.iconochive-mute:before{content:"\1f507"}
+.iconochive-unmute:before{content:"\1f50a"}
+.iconochive-share:before{content:"\1f381"}
+.iconochive-edit:before{content:"\270e"}
+.iconochive-reedit:before{content:"\2710"}
+.iconochive-gear:before{content:"\2699"}
+.iconochive-remove-circle:before{content:"\274e"}
+.iconochive-plus-circle:before{content:"\1f5d6"}
+.iconochive-minus-circle:before{content:"\1f5d5"}
+.iconochive-x:before{content:"\1f5d9"}
+.iconochive-fork:before{content:"\22d4"}
+.iconochive-trash:before{content:"\1f5d1"}
+.iconochive-warning:before{content:"\26a0"}
+.iconochive-flash:before{content:"\1f5f2"}
+.iconochive-world:before{content:"\1f5fa"}
+.iconochive-lock:before{content:"\1f512"}
+.iconochive-unlock:before{content:"\1f513"}
+.iconochive-twitter:before{content:"\1f426"}
+.iconochive-facebook:before{content:"\66"}
+.iconochive-googleplus:before{content:"\67"}
+.iconochive-reddit:before{content:"\1f47d"}
+.iconochive-tumblr:before{content:"\54"}
+.iconochive-pinterest:before{content:"\1d4df"}
+.iconochive-popcorn:before{content:"\1f4a5"}
+.iconochive-email:before{content:"\1f4e7"}
+.iconochive-embed:before{content:"\1f517"}
+.iconochive-gamepad:before{content:"\1f579"}
+.iconochive-Zoom_In:before{content:"\2b"}
+.iconochive-Zoom_Out:before{content:"\2d"}
+.iconochive-RSS:before{content:"\1f4e8"}
+.iconochive-Light_Bulb:before{content:"\1f4a1"}
+.iconochive-Add:before{content:"\2295"}
+.iconochive-Tab_Activity:before{content:"\2318"}
+.iconochive-Forward:before{content:"\23e9"}
+.iconochive-Backward:before{content:"\23ea"}
+.iconochive-No_Audio:before{content:"\1f508"}
+.iconochive-Pause:before{content:"\23f8"}
+.iconochive-No_Favorite:before{content:"\2606"}
+.iconochive-Unike:before{content:"\2661"}
+.iconochive-Song:before{content:"\266b"}
+.iconochive-No_Flag:before{content:"\2690"}
+.iconochive-Flag:before{content:"\2691"}
+.iconochive-Done:before{content:"\2713"}
+.iconochive-Check:before{content:"\2714"}
+.iconochive-Refresh:before{content:"\27f3"}
+.iconochive-Headphones:before{content:"\1f3a7"}
+.iconochive-Chart:before{content:"\1f4c8"}
+.iconochive-Bookmark:before{content:"\1f4d1"}
+.iconochive-Documents:before{content:"\1f4da"}
+.iconochive-Newspaper:before{content:"\1f4f0"}
+.iconochive-Podcast:before{content:"\1f4f6"}
+.iconochive-Radio:before{content:"\1f4fb"}
+.iconochive-Cassette:before{content:"\1f4fc"}
+.iconochive-Shuffle:before{content:"\1f500"}
+.iconochive-Loop:before{content:"\1f501"}
+.iconochive-Low_Audio:before{content:"\1f509"}
+.iconochive-First:before{content:"\1f396"}
+.iconochive-Invisible:before{content:"\1f576"}
+.iconochive-Computer:before{content:"\1f5b3"}
diff --git a/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/style.css b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/style.css
new file mode 100644
index 0000000..1eac881
--- /dev/null
+++ b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/style.css
@@ -0,0 +1,70 @@
+body {
+ max-width: 900px;
+ margin: auto;
+ padding: 8px;
+ font-family: sans-serif;
+ font-size: 14px;
+ //color: white;
+ //background: black;
+}
+
+h {
+ background-color: #e0e0e0;
+ padding: 2px;
+}
+
+h2 {
+ border-bottom: 1px solid;
+ //padding-left: 8px;
+}
+
+img {
+ display: block;
+}
+
+.footer-table {
+ font-size: 14px;
+}
+
+.bordered-table {
+ border-collapse: collapse;
+ font-size: 11px;
+}
+
+.bordered-table th, .bordered-table td {
+ border: 1px solid;
+ padding: 4px;
+}
+
+hr {
+ border: none;
+ border-bottom: 1px solid black;
+}
+
+pre {
+ padding: 8px;
+ font-size: 12px;
+ color: black;
+ background-color: LightGray;
+}
+/*
+ FILE ARCHIVED ON 03:35:25 Aug 27, 2022 AND RETRIEVED FROM THE
+ INTERNET ARCHIVE ON 15:39:09 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: 107.165
+ exclusion.robots: 0.073
+ exclusion.robots.policy: 0.067
+ cdx.remote: 0.064
+ esindex: 0.009
+ LoadShardBlock: 52.713 (3)
+ PetaboxLoader3.datanode: 140.466 (4)
+ CDXLines.iter: 14.771 (3)
+ load_resource: 139.896
+ PetaboxLoader3.resolve: 47.824
+*/
\ No newline at end of file
diff --git a/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/vram-layout.svg b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/vram-layout.svg
new file mode 100644
index 0000000..882055c
--- /dev/null
+++ b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/vram-layout.svg
@@ -0,0 +1,261 @@
+
+
+
+
diff --git a/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/wombat.js b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/wombat.js
new file mode 100644
index 0000000..19d1a47
--- /dev/null
+++ b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/wombat.js
@@ -0,0 +1,21 @@
+/*
+Wombat.js client-side rewriting engine for web archive replay
+Copyright (C) 2014-2020 Webrecorder Software, Rhizome, and Contributors. Released under the GNU Affero General Public License.
+
+This file is part of wombat.js, see https://github.com/webrecorder/wombat.js for the full source
+Wombat.js is part of the Webrecorder project (https://github.com/webrecorder)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+ */
+(function(){function FuncMap(){this._map=[]}function ensureNumber(maybeNumber){try{switch(typeof maybeNumber){case"number":case"bigint":return maybeNumber;}var converted=Number(maybeNumber);return isNaN(converted)?null:converted}catch(e){}return null}function addToStringTagToClass(clazz,tag){typeof self.Symbol!=="undefined"&&typeof self.Symbol.toStringTag!=="undefined"&&Object.defineProperty(clazz.prototype,self.Symbol.toStringTag,{value:tag,enumerable:false})}function autobind(clazz){for(var prop,propValue,proto=clazz.__proto__||clazz.constructor.prototype||clazz.prototype,clazzProps=Object.getOwnPropertyNames(proto),len=clazzProps.length,i=0;i source[srcset], picture > source[data-srcset], picture > source[data-src], video > source[srcset], video > source[data-srcset], video > source[data-src], audio > source[srcset], audio > source[data-srcset], audio > source[data-src]",this.wombat=wombat,this.$wbwindow=wombat.$wbwindow,this.worker=null,autobind(this),this._initWorker(config)):new AutoFetcher(wombat,config)}function wrapSameOriginEventListener(origListener,win){return function wrappedSameOriginEventListener(event){return window==win?origListener(event):void 0}}function wrapEventListener(origListener,obj,wombat){var origListenerFunc;return origListenerFunc=typeof origListener==="function"?origListener:typeof origListener==="object"?origListener.handleEvent.bind(origListener):function(){},function wrappedEventListener(event){var ne;if(event.data&&event.data.from&&event.data.message){if(event.data.to_origin!=="*"&&obj.WB_wombat_location&&!wombat.startsWith(event.data.to_origin,obj.WB_wombat_location.origin))return void console.warn("Skipping message event to "+event.data.to_origin+" doesn't start with origin "+obj.WB_wombat_location.origin);var source=event.source;event.data.from_top?source=obj.__WB_top_frame:event.data.src_id&&obj.__WB_win_id&&obj.__WB_win_id[event.data.src_id]&&(source=obj.__WB_win_id[event.data.src_id]),ne=new MessageEvent("message",{bubbles:event.bubbles,cancelable:event.cancelable,data:event.data.message,origin:event.data.from,lastEventId:event.lastEventId,source:wombat.proxyToObj(source),ports:event.ports}),ne._target=event.target,ne._srcElement=event.srcElement,ne._currentTarget=event.currentTarget,ne._eventPhase=event.eventPhase,ne._path=event.path}else ne=event;return origListenerFunc(ne)}}function Wombat($wbwindow,wbinfo){if(!(this instanceof Wombat))return new Wombat($wbwindow,wbinfo);this.debug_rw=false,this.$wbwindow=$wbwindow,this.HTTP_PREFIX="http://",this.HTTPS_PREFIX="https://",this.REL_PREFIX="//",this.VALID_PREFIXES=[this.HTTP_PREFIX,this.HTTPS_PREFIX,this.REL_PREFIX],this.IGNORE_PREFIXES=["#","about:","data:","blob:","mailto:","javascript:","{","*"],"ignore_prefixes"in wbinfo&&(this.IGNORE_PREFIXES=this.IGNORE_PREFIXES.concat(wbinfo.ignore_prefixes)),this.WB_CHECK_THIS_FUNC="_____WB$wombat$check$this$function_____",this.WB_ASSIGN_FUNC="_____WB$wombat$assign$function_____",this.wb_setAttribute=$wbwindow.Element.prototype.setAttribute,this.wb_getAttribute=$wbwindow.Element.prototype.getAttribute,this.wb_funToString=Function.prototype.toString,this.WBAutoFetchWorker=null,this.wbUseAFWorker=wbinfo.enable_auto_fetch&&$wbwindow.Worker!=null&&wbinfo.is_live,this.wb_rel_prefix="",this.wb_wombat_updating=false,this.message_listeners=new FuncMap,this.storage_listeners=new FuncMap,this.linkAsTypes={script:"js_",worker:"js_",style:"cs_",image:"im_",document:"if_",fetch:"mp_",font:"oe_",audio:"oe_",video:"oe_",embed:"oe_",object:"oe_",track:"oe_","":"mp_",null:"mp_",undefined:"mp_"},this.linkTagMods={linkRelToAs:{import:this.linkAsTypes,preload:this.linkAsTypes},stylesheet:"cs_",null:"mp_",undefined:"mp_","":"mp_"},this.tagToMod={A:{href:"mp_"},AREA:{href:"mp_"},AUDIO:{src:"oe_",poster:"im_"},BASE:{href:"mp_"},EMBED:{src:"oe_"},FORM:{action:"mp_"},FRAME:{src:"fr_"},IFRAME:{src:"if_"},IMAGE:{href:"im_","xlink:href":"im_"},IMG:{src:"im_",srcset:"im_"},INPUT:{src:"oe_"},INS:{cite:"mp_"},META:{content:"mp_"},OBJECT:{data:"oe_",codebase:"oe_"},Q:{cite:"mp_"},SCRIPT:{src:"js_","xlink:href":"js_"},SOURCE:{src:"oe_",srcset:"oe_"},TRACK:{src:"oe_"},VIDEO:{src:"oe_",poster:"im_"},image:{href:"im_","xlink:href":"im_"}},this.URL_PROPS=["href","hash","pathname","host","hostname","protocol","origin","search","port"],this.wb_info=wbinfo,this.wb_opts=wbinfo.wombat_opts,this.wb_replay_prefix=wbinfo.prefix,this.wb_is_proxy=this.wb_info.proxy_magic||!this.wb_replay_prefix,this.wb_info.top_host=this.wb_info.top_host||"*",this.wb_curr_host=$wbwindow.location.protocol+"//"+$wbwindow.location.host,this.wb_info.wombat_opts=this.wb_info.wombat_opts||{},this.wb_orig_scheme=this.wb_info.wombat_scheme+"://",this.wb_orig_origin=this.wb_orig_scheme+this.wb_info.wombat_host,this.wb_abs_prefix=this.wb_replay_prefix,this.wb_capture_date_part="",!this.wb_info.is_live&&this.wb_info.wombat_ts&&(this.wb_capture_date_part="/"+this.wb_info.wombat_ts+"/"),this.BAD_PREFIXES=["http:"+this.wb_replay_prefix,"https:"+this.wb_replay_prefix,"http:/"+this.wb_replay_prefix,"https:/"+this.wb_replay_prefix],this.hostnamePortRe=/^[\w-]+(\.[\w-_]+)+(:\d+)(\/|$)/,this.ipPortRe=/^\d+\.\d+\.\d+\.\d+(:\d+)?(\/|$)/,this.workerBlobRe=/__WB_pmw\(.*?\)\.(?=postMessage\()/g,this.rmCheckThisInjectRe=/_____WB\$wombat\$check\$this\$function_____\(.*?\)/g,this.STYLE_REGEX=/(url\s*\(\s*[\\"']*)([^)'"]+)([\\"']*\s*\))/gi,this.IMPORT_REGEX=/(@import\s*[\\"']*)([^)'";]+)([\\"']*\s*;?)/gi,this.no_wombatRe=/WB_wombat_/g,this.srcsetRe=/\s*(\S*\s+[\d.]+[wx]),|(?:\s*,(?:\s+|(?=https?:)))/,this.cookie_path_regex=/\bPath='?"?([^;'"\s]+)/i,this.cookie_domain_regex=/\bDomain=([^;'"\s]+)/i,this.cookie_expires_regex=/\bExpires=([^;'"]+)/gi,this.SetCookieRe=/,(?![|])/,this.IP_RX=/^(\d)+\.(\d)+\.(\d)+\.(\d)+$/,this.FullHTMLRegex=/^\s*<(?:html|head|body|!doctype html)/i,this.IsTagRegex=/^\s*,this.DotPostMessageRe=/(.postMessage\s*\()/,this.extractPageUnderModiferRE=/\/(?:[0-9]{14})?([a-z]{2, 3}_)\//,this.write_buff="";var eTargetProto=($wbwindow.EventTarget||{}).prototype;this.utilFns={cspViolationListener:function(e){if(console.group("CSP Violation"),console.log("Replayed Page URL",window.WB_wombat_location.href),console.log("The documentURI",e.documentURI),console.log("The blocked URL",e.blockedURI),console.log("The directive violated",e.violatedDirective),console.log("Our policy",e.originalPolicy),e.sourceFile){var fileInfo="File: "+e.sourceFile;e.lineNumber&&e.columnNumber?fileInfo+=" @ "+e.lineNumber+":"+e.columnNumber:e.lineNumber&&(fileInfo+=" @ "+e.lineNumber),console.log(fileInfo)}console.groupEnd()},addEventListener:eTargetProto.addEventListener,removeEventListener:eTargetProto.removeEventListener,objToString:Object.prototype.toString,wbSheetMediaQChecker:null,XHRopen:null,XHRsend:null},this.showCSPViolations={yesNo:false,added:false},autobind(this)}FuncMap.prototype.set=function(fnKey,fnValue){this._map.push([fnKey,fnValue])},FuncMap.prototype.get=function(fnKey){for(var i=0;i=0){var fnMapping=this._map.splice(idx,1);return fnMapping[0][1]}return null},FuncMap.prototype.map=function(param){for(var i=0;i0&&afw.preserveMedia(media)})},AutoFetcher.prototype.terminate=function(){this.worker.terminate()},AutoFetcher.prototype.justFetch=function(urls){this.worker.postMessage({type:"fetch-all",values:urls})},AutoFetcher.prototype.fetchAsPage=function(url,originalUrl,title){if(url){var headers={"X-Wombat-History-Page":originalUrl};if(title){var encodedTitle=encodeURIComponent(title.trim());title&&(headers["X-Wombat-History-Title"]=encodedTitle)}var fetchData={url:url,options:{headers:headers,cache:"no-store"}};this.justFetch([fetchData])}},AutoFetcher.prototype.postMessage=function(msg,deferred){if(deferred){var afWorker=this;return void Promise.resolve().then(function(){afWorker.worker.postMessage(msg)})}this.worker.postMessage(msg)},AutoFetcher.prototype.preserveSrcset=function(srcset,mod){this.postMessage({type:"values",srcset:{value:srcset,mod:mod,presplit:true}},true)},AutoFetcher.prototype.preserveDataSrcset=function(elem){this.postMessage({type:"values",srcset:{value:elem.dataset.srcset,mod:this.rwMod(elem),presplit:false}},true)},AutoFetcher.prototype.preserveMedia=function(media){this.postMessage({type:"values",media:media},true)},AutoFetcher.prototype.getSrcset=function(elem){return this.wombat.wb_getAttribute?this.wombat.wb_getAttribute.call(elem,"srcset"):elem.getAttribute("srcset")},AutoFetcher.prototype.rwMod=function(elem){switch(elem.tagName){case"SOURCE":return elem.parentElement&&elem.parentElement.tagName==="PICTURE"?"im_":"oe_";case"IMG":return"im_";}return"oe_"},AutoFetcher.prototype.extractFromLocalDoc=function(){var afw=this;Promise.resolve().then(function(){for(var msg={type:"values",context:{docBaseURI:document.baseURI}},media=[],i=0,sheets=document.styleSheets;i=0)||scriptType.indexOf("text/template")>=0)},Wombat.prototype.skipWrapScriptTextBasedOnText=function(text){if(!text||text.indexOf(this.WB_ASSIGN_FUNC)>=0||text.indexOf("<")===0)return true;for(var override_props=["window","self","document","location","top","parent","frames","opener"],i=0;i=0)return false;return true},Wombat.prototype.nodeHasChildren=function(node){if(!node)return false;if(typeof node.hasChildNodes==="function")return node.hasChildNodes();var kids=node.children||node.childNodes;return!!kids&&kids.length>0},Wombat.prototype.rwModForElement=function(elem,attrName){if(!elem)return undefined;var mod="mp_";if(!(elem.tagName==="LINK"&&attrName==="href")){var maybeMod=this.tagToMod[elem.tagName];maybeMod!=null&&(mod=maybeMod[attrName])}else if(elem.rel){var relV=elem.rel.trim().toLowerCase(),asV=this.wb_getAttribute.call(elem,"as");if(asV&&this.linkTagMods.linkRelToAs[relV]!=null){var asMods=this.linkTagMods.linkRelToAs[relV];mod=asMods[asV.toLowerCase()]}else this.linkTagMods[relV]!=null&&(mod=this.linkTagMods[relV])}return mod},Wombat.prototype.removeWBOSRC=function(elem){elem.tagName!=="SCRIPT"||elem.__$removedWBOSRC$__||(elem.hasAttribute("__wb_orig_src")&&elem.removeAttribute("__wb_orig_src"),elem.__$removedWBOSRC$__=true)},Wombat.prototype.retrieveWBOSRC=function(elem){if(elem.tagName==="SCRIPT"&&!elem.__$removedWBOSRC$__){var maybeWBOSRC;return maybeWBOSRC=this.wb_getAttribute?this.wb_getAttribute.call(elem,"__wb_orig_src"):elem.getAttribute("__wb_orig_src"),maybeWBOSRC==null&&(elem.__$removedWBOSRC$__=true),maybeWBOSRC}return undefined},Wombat.prototype.wrapScriptTextJsProxy=function(scriptText){return"var _____WB$wombat$assign$function_____ = function(name) {return (self._wb_wombat && self._wb_wombat.local_init && self._wb_wombat.local_init(name)) || self[name]; };\nif (!self.__WB_pmw) { self.__WB_pmw = function(obj) { this.__WB_source = obj; return this; } }\n{\nlet window = _____WB$wombat$assign$function_____(\"window\");\nlet self = _____WB$wombat$assign$function_____(\"self\");\nlet document = _____WB$wombat$assign$function_____(\"document\");\nlet location = _____WB$wombat$assign$function_____(\"location\");\nlet top = _____WB$wombat$assign$function_____(\"top\");\nlet parent = _____WB$wombat$assign$function_____(\"parent\");\nlet frames = _____WB$wombat$assign$function_____(\"frames\");\nlet opener = _____WB$wombat$assign$function_____(\"opener\");\n"+scriptText.replace(this.DotPostMessageRe,".__WB_pmw(self.window)$1")+"\n\n}"},Wombat.prototype.watchElem=function(elem,func){if(!this.$wbwindow.MutationObserver)return false;var m=new this.$wbwindow.MutationObserver(function(records,observer){for(var r,i=0;i"},Wombat.prototype.getFinalUrl=function(useRel,mod,url){var prefix=useRel?this.wb_rel_prefix:this.wb_abs_prefix;return mod==null&&(mod=this.wb_info.mod),this.wb_info.is_live||(prefix+=this.wb_info.wombat_ts),prefix+=mod,prefix[prefix.length-1]!=="/"&&(prefix+="/"),prefix+url},Wombat.prototype.resolveRelUrl=function(url,doc){var docObj=doc||this.$wbwindow.document,parser=this.makeParser(docObj.baseURI,docObj),hash=parser.href.lastIndexOf("#"),href=hash>=0?parser.href.substring(0,hash):parser.href,lastslash=href.lastIndexOf("/");return parser.href=lastslash>=0&&lastslash!==href.length-1?href.substring(0,lastslash+1)+url:href+url,parser.href},Wombat.prototype.extractOriginalURL=function(rewrittenUrl){if(!rewrittenUrl)return"";if(this.wb_is_proxy)return rewrittenUrl;var rwURLString=rewrittenUrl.toString(),url=rwURLString;if(this.startsWithOneOf(url,this.IGNORE_PREFIXES))return url;var start;start=this.startsWith(url,this.wb_abs_prefix)?this.wb_abs_prefix.length:this.wb_rel_prefix&&this.startsWith(url,this.wb_rel_prefix)?this.wb_rel_prefix.length:this.wb_rel_prefix?1:0;var index=url.indexOf("/http",start);return index<0&&(index=url.indexOf("///",start)),index<0&&(index=url.indexOf("/blob:",start)),index<0&&(index=url.indexOf("/about:blank",start)),index>=0?url=url.substr(index+1):(index=url.indexOf(this.wb_replay_prefix),index>=0&&(url=url.substr(index+this.wb_replay_prefix.length)),url.length>4&&url.charAt(2)==="_"&&url.charAt(3)==="/"&&(url=url.substr(4)),url!==rwURLString&&!this.startsWithOneOf(url,this.VALID_PREFIXES)&&!this.startsWith(url,"blob:")&&(url=this.wb_orig_scheme+url)),rwURLString.charAt(0)==="/"&&rwURLString.charAt(1)!=="/"&&this.startsWith(url,this.wb_orig_origin)&&(url=url.substr(this.wb_orig_origin.length)),this.startsWith(url,this.REL_PREFIX)?this.wb_info.wombat_scheme+":"+url:url},Wombat.prototype.makeParser=function(maybeRewrittenURL,doc){var originalURL=this.extractOriginalURL(maybeRewrittenURL),docElem=doc;return doc||(this.$wbwindow.location.href==="about:blank"&&this.$wbwindow.opener?docElem=this.$wbwindow.opener.document:docElem=this.$wbwindow.document),this._makeURLParser(originalURL,docElem)},Wombat.prototype._makeURLParser=function(url,docElem){try{return new this.$wbwindow.URL(url,docElem.baseURI)}catch(e){}var p=docElem.createElement("a");return p._no_rewrite=true,p.href=url,p},Wombat.prototype.defProp=function(obj,prop,setFunc,getFunc,enumerable){var existingDescriptor=Object.getOwnPropertyDescriptor(obj,prop);if(existingDescriptor&&!existingDescriptor.configurable)return false;if(!getFunc)return false;var descriptor={configurable:true,enumerable:enumerable||false,get:getFunc};setFunc&&(descriptor.set=setFunc);try{return Object.defineProperty(obj,prop,descriptor),true}catch(e){return console.warn("Failed to redefine property %s",prop,e.message),false}},Wombat.prototype.defGetterProp=function(obj,prop,getFunc,enumerable){var existingDescriptor=Object.getOwnPropertyDescriptor(obj,prop);if(existingDescriptor&&!existingDescriptor.configurable)return false;if(!getFunc)return false;try{return Object.defineProperty(obj,prop,{configurable:true,enumerable:enumerable||false,get:getFunc}),true}catch(e){return console.warn("Failed to redefine property %s",prop,e.message),false}},Wombat.prototype.getOrigGetter=function(obj,prop){var orig_getter;if(obj.__lookupGetter__&&(orig_getter=obj.__lookupGetter__(prop)),!orig_getter&&Object.getOwnPropertyDescriptor){var props=Object.getOwnPropertyDescriptor(obj,prop);props&&(orig_getter=props.get)}return orig_getter},Wombat.prototype.getOrigSetter=function(obj,prop){var orig_setter;if(obj.__lookupSetter__&&(orig_setter=obj.__lookupSetter__(prop)),!orig_setter&&Object.getOwnPropertyDescriptor){var props=Object.getOwnPropertyDescriptor(obj,prop);props&&(orig_setter=props.set)}return orig_setter},Wombat.prototype.getAllOwnProps=function(obj){for(var ownProps=[],props=Object.getOwnPropertyNames(obj),i=0;i "+final_href),actualLocation.href=final_href}}},Wombat.prototype.checkLocationChange=function(wombatLoc,isTop){var locType=typeof wombatLoc,actual_location=isTop?this.$wbwindow.__WB_replay_top.location:this.$wbwindow.location;locType==="string"?this.updateLocation(wombatLoc,actual_location.href,actual_location):locType==="object"&&this.updateLocation(wombatLoc.href,wombatLoc._orig_href,actual_location)},Wombat.prototype.checkAllLocations=function(){return!this.wb_wombat_updating&&void(this.wb_wombat_updating=true,this.checkLocationChange(this.$wbwindow.WB_wombat_location,false),this.$wbwindow.WB_wombat_location!=this.$wbwindow.__WB_replay_top.WB_wombat_location&&this.checkLocationChange(this.$wbwindow.__WB_replay_top.WB_wombat_location,true),this.wb_wombat_updating=false)},Wombat.prototype.proxyToObj=function(source){if(source)try{var proxyRealObj=source.__WBProxyRealObj__;if(proxyRealObj)return proxyRealObj}catch(e){}return source},Wombat.prototype.objToProxy=function(obj){if(obj)try{var maybeWbProxy=obj._WB_wombat_obj_proxy;if(maybeWbProxy)return maybeWbProxy}catch(e){}return obj},Wombat.prototype.defaultProxyGet=function(obj,prop,ownProps,fnCache){switch(prop){case"__WBProxyRealObj__":return obj;case"location":case"WB_wombat_location":return obj.WB_wombat_location;case"_WB_wombat_obj_proxy":return obj._WB_wombat_obj_proxy;case"__WB_pmw":case"WB_wombat_eval":case this.WB_ASSIGN_FUNC:case this.WB_CHECK_THIS_FUNC:return obj[prop];case"origin":return obj.WB_wombat_location.origin;case"constructor":if(obj.constructor===Window)return obj.constructor;}var retVal=obj[prop],type=typeof retVal;if(type==="function"&&ownProps.indexOf(prop)!==-1){switch(prop){case"requestAnimationFrame":case"cancelAnimationFrame":{if(!this.isNativeFunction(retVal))return retVal;break}}var cachedFN=fnCache[prop];return cachedFN&&cachedFN.original===retVal||(cachedFN={original:retVal,boundFn:retVal.bind(obj)},fnCache[prop]=cachedFN),cachedFN.boundFn}return type==="object"&&retVal&&retVal._WB_wombat_obj_proxy?(retVal instanceof Window&&this.initNewWindowWombat(retVal),retVal._WB_wombat_obj_proxy):retVal},Wombat.prototype.setLoc=function(loc,originalURL){var parser=this.makeParser(originalURL,loc.ownerDocument);loc._orig_href=originalURL,loc._parser=parser;var href=parser.href;loc._hash=parser.hash,loc._href=href,loc._host=parser.host,loc._hostname=parser.hostname,loc._origin=parser.origin?parser.host?parser.origin:"null":parser.protocol+"//"+parser.hostname+(parser.port?":"+parser.port:""),loc._pathname=parser.pathname,loc._port=parser.port,loc._protocol=parser.protocol,loc._search=parser.search,Object.defineProperty||(loc.href=href,loc.hash=parser.hash,loc.host=loc._host,loc.hostname=loc._hostname,loc.origin=loc._origin,loc.pathname=loc._pathname,loc.port=loc._port,loc.protocol=loc._protocol,loc.search=loc._search)},Wombat.prototype.makeGetLocProp=function(prop,origGetter){var wombat=this;return function newGetLocProp(){if(this._no_rewrite)return origGetter.call(this,prop);var curr_orig_href=origGetter.call(this,"href");return prop==="href"?wombat.extractOriginalURL(curr_orig_href):prop==="ancestorOrigins"?[]:(this._orig_href!==curr_orig_href&&wombat.setLoc(this,curr_orig_href),this["_"+prop])}},Wombat.prototype.makeSetLocProp=function(prop,origSetter,origGetter){var wombat=this;return function newSetLocProp(value){if(this._no_rewrite)return origSetter.call(this,prop,value);if(this["_"+prop]!==value){if(this["_"+prop]=value,!this._parser){var href=origGetter.call(this);this._parser=wombat.makeParser(href,this.ownerDocument)}var rel=false;prop==="href"&&typeof value==="string"&&value&&(value[0]==="."?value=wombat.resolveRelUrl(value,this.ownerDocument):value[0]==="/"&&(value.length<=1||value[1]!=="/")&&(rel=true,value=WB_wombat_location.origin+value));try{this._parser[prop]=value}catch(e){console.log("Error setting "+prop+" = "+value)}prop==="hash"?(value=this._parser[prop],origSetter.call(this,"hash",value)):(rel=rel||value===this._parser.pathname,value=wombat.rewriteUrl(this._parser.href,rel),origSetter.call(this,"href",value))}}},Wombat.prototype.styleReplacer=function(match,n1,n2,n3,offset,string){return n1+this.rewriteUrl(n2)+n3},Wombat.prototype.domConstructorErrorChecker=function(thisObj,what,args,numRequiredArgs){var errorMsg,needArgs=typeof numRequiredArgs==="number"?numRequiredArgs:1;if(thisObj instanceof Window?errorMsg="Failed to construct '"+what+"': Please use the 'new' operator, this DOM object constructor cannot be called as a function.":args&&args.length=0)return url;if(url.indexOf(this.wb_rel_prefix)===0&&url.indexOf("http")>1){var scheme_sep=url.indexOf(":/");return scheme_sep>0&&url[scheme_sep+2]!=="/"?url.substring(0,scheme_sep+2)+"/"+url.substring(scheme_sep+2):url}return this.getFinalUrl(true,mod,this.wb_orig_origin+url)}url.charAt(0)==="."&&(url=this.resolveRelUrl(url,doc));var prefix=this.startsWithOneOf(url.toLowerCase(),this.VALID_PREFIXES);if(prefix){var orig_host=this.$wbwindow.__WB_replay_top.location.host,orig_protocol=this.$wbwindow.__WB_replay_top.location.protocol,prefix_host=prefix+orig_host+"/";if(this.startsWith(url,prefix_host)){if(this.startsWith(url,this.wb_replay_prefix))return url;var curr_scheme=orig_protocol+"//",path=url.substring(prefix_host.length),rebuild=false;return path.indexOf(this.wb_rel_prefix)<0&&url.indexOf("/static/")<0&&(path=this.getFinalUrl(true,mod,WB_wombat_location.origin+"/"+path),rebuild=true),prefix!==curr_scheme&&prefix!==this.REL_PREFIX&&(rebuild=true),rebuild&&(url=useRel?"":curr_scheme+orig_host,path&&path[0]!=="/"&&(url+="/"),url+=path),url}return this.getFinalUrl(useRel,mod,url)}return prefix=this.startsWithOneOf(url,this.BAD_PREFIXES),prefix?this.getFinalUrl(useRel,mod,this.extractOriginalURL(url)):this.isHostUrl(url)&&!this.startsWith(url,originalLoc.host+"/")?this.getFinalUrl(useRel,mod,this.wb_orig_scheme+url):url},Wombat.prototype.rewriteUrl=function(url,useRel,mod,doc){var rewritten=this.rewriteUrl_(url,useRel,mod,doc);return this.debug_rw&&(url===rewritten?console.log("NOT REWRITTEN "+url):console.log("REWRITE: "+url+" -> "+rewritten)),rewritten},Wombat.prototype.performAttributeRewrite=function(elem,name,value,absUrlOnly){switch(name){case"innerHTML":case"outerHTML":return this.rewriteHtml(value);case"filter":return this.rewriteInlineStyle(value);case"style":return this.rewriteStyle(value);case"srcset":return this.rewriteSrcset(value,elem);}if(absUrlOnly&&!this.startsWithOneOf(value,this.VALID_PREFIXES))return value;var mod=this.rwModForElement(elem,name);return this.wbUseAFWorker&&this.WBAutoFetchWorker&&this.isSavedDataSrcSrcset(elem)&&this.WBAutoFetchWorker.preserveDataSrcset(elem),this.rewriteUrl(value,false,mod,elem.ownerDocument)},Wombat.prototype.rewriteAttr=function(elem,name,absUrlOnly){var changed=false;if(!elem||!elem.getAttribute||elem._no_rewrite||elem["_"+name])return changed;var value=this.wb_getAttribute.call(elem,name);if(!value||this.startsWith(value,"javascript:"))return changed;var new_value=this.performAttributeRewrite(elem,name,value,absUrlOnly);return new_value!==value&&(this.removeWBOSRC(elem),this.wb_setAttribute.call(elem,name,new_value),changed=true),changed},Wombat.prototype.noExceptRewriteStyle=function(style){try{return this.rewriteStyle(style)}catch(e){return style}},Wombat.prototype.rewriteStyle=function(style){if(!style)return style;var value=style;return typeof style==="object"&&(value=style.toString()),typeof value==="string"?value.replace(this.STYLE_REGEX,this.styleReplacer).replace(this.IMPORT_REGEX,this.styleReplacer).replace(this.no_wombatRe,""):value},Wombat.prototype.rewriteSrcset=function(value,elem){if(!value)return"";for(var split=value.split(this.srcsetRe),values=[],mod=this.rwModForElement(elem,"srcset"),i=0;i=0){var JS="javascript:";new_value="javascript:window.parent._wb_wombat.initNewWindowWombat(window);"+value.substr(11)}return new_value||(new_value=this.rewriteUrl(value,false,this.rwModForElement(elem,attrName))),new_value!==value&&(this.wb_setAttribute.call(elem,attrName,new_value),true)},Wombat.prototype.rewriteScript=function(elem){if(elem.hasAttribute("src")||!elem.textContent||!this.$wbwindow.Proxy)return this.rewriteAttr(elem,"src");if(this.skipWrapScriptBasedOnType(elem.type))return false;var text=elem.textContent.trim();return!this.skipWrapScriptTextBasedOnText(text)&&(elem.textContent=this.wrapScriptTextJsProxy(text),true)},Wombat.prototype.rewriteSVGElem=function(elem){var changed=this.rewriteAttr(elem,"filter");return changed=this.rewriteAttr(elem,"style")||changed,changed=this.rewriteAttr(elem,"xlink:href")||changed,changed=this.rewriteAttr(elem,"href")||changed,changed=this.rewriteAttr(elem,"src")||changed,changed},Wombat.prototype.rewriteElem=function(elem){var changed=false;if(!elem)return changed;if(elem instanceof SVGElement)changed=this.rewriteSVGElem(elem);else switch(elem.tagName){case"META":var maybeCSP=this.wb_getAttribute.call(elem,"http-equiv");maybeCSP&&maybeCSP.toLowerCase()==="content-security-policy"&&(this.wb_setAttribute.call(elem,"http-equiv","_"+maybeCSP),changed=true);break;case"STYLE":var new_content=this.rewriteStyle(elem.textContent);elem.textContent!==new_content&&(elem.textContent=new_content,changed=true,this.wbUseAFWorker&&this.WBAutoFetchWorker&&elem.sheet!=null&&this.WBAutoFetchWorker.deferredSheetExtraction(elem.sheet));break;case"LINK":changed=this.rewriteAttr(elem,"href"),this.wbUseAFWorker&&elem.rel==="stylesheet"&&this._addEventListener(elem,"load",this.utilFns.wbSheetMediaQChecker);break;case"IMG":changed=this.rewriteAttr(elem,"src"),changed=this.rewriteAttr(elem,"srcset")||changed,changed=this.rewriteAttr(elem,"style")||changed,this.wbUseAFWorker&&this.WBAutoFetchWorker&&elem.dataset.srcset&&this.WBAutoFetchWorker.preserveDataSrcset(elem);break;case"OBJECT":if(this.wb_info.isSW&&elem.parentElement&&elem.getAttribute("type")==="application/pdf"){for(var iframe=this.$wbwindow.document.createElement("IFRAME"),i=0;i0;)for(var child,children=rewriteQ.shift(),i=0;i"+rwString+"","text/html");if(!inner_doc||!this.nodeHasChildren(inner_doc.head)||!inner_doc.head.children[0].content)return rwString;var template=inner_doc.head.children[0];if(template._no_rewrite=true,this.recurseRewriteElem(template.content)){var new_html=template.innerHTML;if(checkEndTag){var first_elem=template.content.children&&template.content.children[0];if(first_elem){var end_tag=""+first_elem.tagName.toLowerCase()+">";this.endsWith(new_html,end_tag)&&!this.endsWith(rwString.toLowerCase(),end_tag)&&(new_html=new_html.substring(0,new_html.length-end_tag.length))}else if(rwString[0]!=="<"||rwString[rwString.length-1]!==">")return this.write_buff+=rwString,undefined}return new_html}return rwString},Wombat.prototype.rewriteHtmlFull=function(string,checkEndTag){var inner_doc=new DOMParser().parseFromString(string,"text/html");if(!inner_doc)return string;for(var changed=false,i=0;i=0)inner_doc.documentElement._no_rewrite=true,new_html=this.reconstructDocType(inner_doc.doctype)+inner_doc.documentElement.outerHTML;else{inner_doc.head._no_rewrite=true,inner_doc.body._no_rewrite=true;var headHasKids=this.nodeHasChildren(inner_doc.head),bodyHasKids=this.nodeHasChildren(inner_doc.body);if(new_html=(headHasKids?inner_doc.head.outerHTML:"")+(bodyHasKids?inner_doc.body.outerHTML:""),checkEndTag)if(inner_doc.all.length>3){var end_tag=""+inner_doc.all[3].tagName.toLowerCase()+">";this.endsWith(new_html,end_tag)&&!this.endsWith(string.toLowerCase(),end_tag)&&(new_html=new_html.substring(0,new_html.length-end_tag.length))}else if(string[0]!=="<"||string[string.length-1]!==">")return void(this.write_buff+=string);new_html=this.reconstructDocType(inner_doc.doctype)+new_html}return new_html}return string},Wombat.prototype.rewriteInlineStyle=function(orig){var decoded;try{decoded=decodeURIComponent(orig)}catch(e){decoded=orig}if(decoded!==orig){var parts=this.rewriteStyle(decoded).split(",",2);return parts[0]+","+encodeURIComponent(parts[1])}return this.rewriteStyle(orig)},Wombat.prototype.rewriteCookie=function(cookie){var wombat=this,rwCookie=cookie.replace(this.wb_abs_prefix,"").replace(this.wb_rel_prefix,"");return rwCookie=rwCookie.replace(this.cookie_domain_regex,function(m,m1){var message={domain:m1,cookie:rwCookie,wb_type:"cookie"};return wombat.sendTopMessage(message,true),wombat.$wbwindow.location.hostname.indexOf(".")>=0&&!wombat.IP_RX.test(wombat.$wbwindow.location.hostname)?"Domain=."+wombat.$wbwindow.location.hostname:""}).replace(this.cookie_path_regex,function(m,m1){var rewritten=wombat.rewriteUrl(m1);return rewritten.indexOf(wombat.wb_curr_host)===0&&(rewritten=rewritten.substring(wombat.wb_curr_host.length)),"Path="+rewritten}),wombat.$wbwindow.location.protocol!=="https:"&&(rwCookie=rwCookie.replace("secure","")),rwCookie.replace(",|",",")},Wombat.prototype.rewriteWorker=function(workerUrl){if(!workerUrl)return workerUrl;var isBlob=workerUrl.indexOf("blob:")===0,isJS=workerUrl.indexOf("javascript:")===0;if(!isBlob&&!isJS){if(!this.startsWithOneOf(workerUrl,this.VALID_PREFIXES)&&!this.startsWith(workerUrl,"/")&&!this.startsWithOneOf(workerUrl,this.BAD_PREFIXES)){var rurl=this.resolveRelUrl(workerUrl,this.$wbwindow.document);return this.rewriteUrl(rurl,false,"wkr_",this.$wbwindow.document)}return this.rewriteUrl(workerUrl,false,"wkr_",this.$wbwindow.document)}var workerCode=isJS?workerUrl.replace("javascript:",""):null;if(isBlob){var x=new XMLHttpRequest;this.utilFns.XHRopen.call(x,"GET",workerUrl,false),this.utilFns.XHRsend.call(x),workerCode=x.responseText.replace(this.workerBlobRe,"").replace(this.rmCheckThisInjectRe,"this")}if(this.wb_info.static_prefix||this.wb_info.ww_rw_script){var originalURL=this.$wbwindow.document.baseURI,ww_rw=this.wb_info.ww_rw_script||this.wb_info.static_prefix+"wombatWorkers.js",rw="(function() { self.importScripts('"+ww_rw+"'); new WBWombat({'prefix': '"+this.wb_abs_prefix+"', 'prefixMod': '"+this.wb_abs_prefix+"wkrf_/', 'originalURL': '"+originalURL+"'}); })();";workerCode=rw+workerCode}var blob=new Blob([workerCode],{type:"application/javascript"});return URL.createObjectURL(blob)},Wombat.prototype.rewriteTextNodeFn=function(fnThis,originalFn,argsObj){var args,deproxiedThis=this.proxyToObj(fnThis);if(argsObj.length>0&&deproxiedThis.parentElement&&deproxiedThis.parentElement.tagName==="STYLE"){args=new Array(argsObj.length);var dataIndex=argsObj.length-1;dataIndex===2?(args[0]=argsObj[0],args[1]=argsObj[1]):dataIndex===1&&(args[0]=argsObj[0]),args[dataIndex]=this.rewriteStyle(argsObj[dataIndex])}else args=argsObj;return originalFn.__WB_orig_apply?originalFn.__WB_orig_apply(deproxiedThis,args):originalFn.apply(deproxiedThis,args)},Wombat.prototype.rewriteDocWriteWriteln=function(fnThis,originalFn,argsObj){var string,thisObj=this.proxyToObj(fnThis),argLen=argsObj.length;if(argLen===0)return originalFn.call(thisObj);string=argLen===1?argsObj[0]:Array.prototype.join.call(argsObj,"");var new_buff=this.rewriteHtml(string,true),res=originalFn.call(thisObj,new_buff);return this.initNewWindowWombat(thisObj.defaultView),res},Wombat.prototype.rewriteChildNodeFn=function(fnThis,originalFn,argsObj){var thisObj=this.proxyToObj(fnThis);if(argsObj.length===0)return originalFn.call(thisObj);var newArgs=this.rewriteElementsInArguments(argsObj);return originalFn.__WB_orig_apply?originalFn.__WB_orig_apply(thisObj,newArgs):originalFn.apply(thisObj,newArgs)},Wombat.prototype.rewriteInsertAdjHTMLOrElemArgs=function(fnThis,originalFn,position,textOrElem,rwHTML){var fnThisObj=this.proxyToObj(fnThis);return fnThisObj._no_rewrite?originalFn.call(fnThisObj,position,textOrElem):rwHTML?originalFn.call(fnThisObj,position,this.rewriteHtml(textOrElem)):(this.rewriteElemComplete(textOrElem),originalFn.call(fnThisObj,position,textOrElem))},Wombat.prototype.rewriteSetTimeoutInterval=function(fnThis,originalFn,argsObj){var rw=this.isString(argsObj[0]),args=rw?new Array(argsObj.length):argsObj;if(rw){args[0]=this.$wbwindow.Proxy?this.wrapScriptTextJsProxy(argsObj[0]):argsObj[0].replace(/\blocation\b/g,"WB_wombat_$&");for(var i=1;i0&&cssStyleValueOverride(this.$wbwindow.CSSStyleValue,"parse"),this.$wbwindow.CSSStyleValue.parseAll&&this.$wbwindow.CSSStyleValue.parseAll.toString().indexOf("[native code]")>0&&cssStyleValueOverride(this.$wbwindow.CSSStyleValue,"parseAll")}if(this.$wbwindow.CSSKeywordValue&&this.$wbwindow.CSSKeywordValue.prototype){var oCSSKV=this.$wbwindow.CSSKeywordValue;this.$wbwindow.CSSKeywordValue=function(CSSKeywordValue_){return function CSSKeywordValue(cssValue){return wombat.domConstructorErrorChecker(this,"CSSKeywordValue",arguments),new CSSKeywordValue_(wombat.rewriteStyle(cssValue))}}(this.$wbwindow.CSSKeywordValue),this.$wbwindow.CSSKeywordValue.prototype=oCSSKV.prototype,Object.defineProperty(this.$wbwindow.CSSKeywordValue.prototype,"constructor",{value:this.$wbwindow.CSSKeywordValue}),addToStringTagToClass(this.$wbwindow.CSSKeywordValue,"CSSKeywordValue")}if(this.$wbwindow.StylePropertyMap&&this.$wbwindow.StylePropertyMap.prototype){var originalSet=this.$wbwindow.StylePropertyMap.prototype.set;this.$wbwindow.StylePropertyMap.prototype.set=function set(){if(arguments.length<=1)return originalSet.__WB_orig_apply?originalSet.__WB_orig_apply(this,arguments):originalSet.apply(this,arguments);var newArgs=new Array(arguments.length);newArgs[0]=arguments[0];for(var i=1;i")&&(array[0]=wombat.rewriteHtml(array[0]),options.type="text/html"),new Blob_(array,options)}}(this.$wbwindow.Blob),this.$wbwindow.Blob.prototype=orig_blob.prototype}},Wombat.prototype.initDocTitleOverride=function(){var orig_get_title=this.getOrigGetter(this.$wbwindow.document,"title"),orig_set_title=this.getOrigSetter(this.$wbwindow.document,"title"),wombat=this,set_title=function title(value){var res=orig_set_title.call(this,value),message={wb_type:"title",title:value};return wombat.sendTopMessage(message),res};this.defProp(this.$wbwindow.document,"title",set_title,orig_get_title)},Wombat.prototype.initFontFaceOverride=function(){if(this.$wbwindow.FontFace){var wombat=this,origFontFace=this.$wbwindow.FontFace;this.$wbwindow.FontFace=function(FontFace_){return function FontFace(family,source,descriptors){wombat.domConstructorErrorChecker(this,"FontFace",arguments,2);var rwSource=source;return source!=null&&(typeof source==="string"?rwSource=wombat.rewriteInlineStyle(source):rwSource=wombat.rewriteInlineStyle(source.toString())),new FontFace_(family,rwSource,descriptors)}}(this.$wbwindow.FontFace),this.$wbwindow.FontFace.prototype=origFontFace.prototype,Object.defineProperty(this.$wbwindow.FontFace.prototype,"constructor",{value:this.$wbwindow.FontFace}),addToStringTagToClass(this.$wbwindow.FontFace,"FontFace")}},Wombat.prototype.initFixedRatio=function(){try{this.$wbwindow.devicePixelRatio=1}catch(e){}if(Object.defineProperty)try{Object.defineProperty(this.$wbwindow,"devicePixelRatio",{value:1,writable:false})}catch(e){}},Wombat.prototype.initPaths=function(wbinfo){wbinfo.wombat_opts=wbinfo.wombat_opts||{},Object.assign(this.wb_info,wbinfo),this.wb_opts=wbinfo.wombat_opts,this.wb_replay_prefix=wbinfo.prefix,this.wb_is_proxy=wbinfo.proxy_magic||!this.wb_replay_prefix,this.wb_info.top_host=this.wb_info.top_host||"*",this.wb_curr_host=this.$wbwindow.location.protocol+"//"+this.$wbwindow.location.host,this.wb_info.wombat_opts=this.wb_info.wombat_opts||{},this.wb_orig_scheme=wbinfo.wombat_scheme+"://",this.wb_orig_origin=this.wb_orig_scheme+wbinfo.wombat_host,this.wb_abs_prefix=this.wb_replay_prefix,this.wb_capture_date_part=!wbinfo.is_live&&wbinfo.wombat_ts?"/"+wbinfo.wombat_ts+"/":"",this.initBadPrefixes(this.wb_replay_prefix),this.initCookiePreset()},Wombat.prototype.initSeededRandom=function(seed){this.$wbwindow.Math.seed=parseInt(seed);var wombat=this;this.$wbwindow.Math.random=function random(){return wombat.$wbwindow.Math.seed=(wombat.$wbwindow.Math.seed*9301+49297)%233280,wombat.$wbwindow.Math.seed/233280}},Wombat.prototype.initHistoryOverrides=function(){this.overrideHistoryFunc("pushState"),this.overrideHistoryFunc("replaceState");var wombat=this;this.$wbwindow.addEventListener("popstate",function(event){wombat.sendHistoryUpdate(wombat.$wbwindow.WB_wombat_location.href,wombat.$wbwindow.document.title)})},Wombat.prototype.initCookiePreset=function(){if(this.wb_info.presetCookie)for(var splitCookies=this.wb_info.presetCookie.split(";"),i=0;i0?"&":"?")+value.toString(),value=null):contentType==="application/json"||contentType==="text/plain"?(this.__WB_xhr_open_arguments[0]="GET",this.__WB_xhr_open_arguments[1]+=(this.__WB_xhr_open_arguments[1].indexOf("?")>0?"&":"?")+jsonToQueryString(value),value=null):wombat.startsWith(contentType,"multipart/form-data")&&(this.__WB_xhr_open_arguments[0]="GET",this.__WB_xhr_open_arguments[1]+=(this.__WB_xhr_open_arguments[1].indexOf("?")>0?"&":"?")+mfdToQueryString(value,contentType))}if(this.__WB_xhr_open_arguments.length>2&&(this.__WB_xhr_open_arguments[2]=true),this._no_rewrite||(this.__WB_xhr_open_arguments[1]=wombat.rewriteUrl(this.__WB_xhr_open_arguments[1])),origOpen.apply(this,this.__WB_xhr_open_arguments),!wombat.startsWith(this.__WB_xhr_open_arguments[1],"data:")){for(const[name,value]of this.__WB_xhr_headers.entries())origSetRequestHeader.call(this,name,value);origSetRequestHeader.call(this,"X-Pywb-Requested-With","XMLHttpRequest")}origSend.call(this,value)}}if(this.$wbwindow.fetch){var orig_fetch=this.$wbwindow.fetch;this.$wbwindow.fetch=function fetch(input,init_opts){var rwInput=input,inputType=typeof input;if(inputType==="string")rwInput=wombat.rewriteUrl(input);else if(inputType==="object"&&input.url){var new_url=wombat.rewriteUrl(input.url);new_url!==input.url&&(rwInput=new Request(new_url,init_opts))}else inputType==="object"&&input.href&&(rwInput=wombat.rewriteUrl(input.href));if(init_opts||(init_opts={}),init_opts.credentials===undefined)try{init_opts.credentials="include"}catch(e){}return orig_fetch.call(wombat.proxyToObj(this),rwInput,init_opts)}}if(this.$wbwindow.Request&&this.$wbwindow.Request.prototype){var orig_request=this.$wbwindow.Request;this.$wbwindow.Request=function(Request_){return function Request(input,init_opts){wombat.domConstructorErrorChecker(this,"Request",arguments);var newInitOpts=init_opts||{},newInput=input,inputType=typeof input;switch(inputType){case"string":newInput=wombat.rewriteUrl(input);break;case"object":if(newInput=input,input.url){var new_url=wombat.rewriteUrl(input.url);new_url!==input.url&&(newInput=new Request_(new_url,input))}else input.href&&(newInput=wombat.rewriteUrl(input.toString(),true));}return newInitOpts.credentials="include",new Request_(newInput,newInitOpts)}}(this.$wbwindow.Request),this.$wbwindow.Request.prototype=orig_request.prototype,Object.defineProperty(this.$wbwindow.Request.prototype,"constructor",{value:this.$wbwindow.Request})}if(this.$wbwindow.Response&&this.$wbwindow.Response.prototype){var originalRedirect=this.$wbwindow.Response.prototype.redirect;this.$wbwindow.Response.prototype.redirect=function redirect(url,status){var rwURL=wombat.rewriteUrl(url,true,null,wombat.$wbwindow.document);return originalRedirect.call(this,rwURL,status)}}if(this.$wbwindow.EventSource&&this.$wbwindow.EventSource.prototype){var origEventSource=this.$wbwindow.EventSource;this.$wbwindow.EventSource=function(EventSource_){return function EventSource(url,configuration){wombat.domConstructorErrorChecker(this,"EventSource",arguments);var rwURL=url;return url!=null&&(rwURL=wombat.rewriteUrl(url)),new EventSource_(rwURL,configuration)}}(this.$wbwindow.EventSource),this.$wbwindow.EventSource.prototype=origEventSource.prototype,Object.defineProperty(this.$wbwindow.EventSource.prototype,"constructor",{value:this.$wbwindow.EventSource}),addToStringTagToClass(this.$wbwindow.EventSource,"EventSource")}if(this.$wbwindow.WebSocket&&this.$wbwindow.WebSocket.prototype){var origWebSocket=this.$wbwindow.WebSocket;this.$wbwindow.WebSocket=function(WebSocket_){return function WebSocket(url,configuration){wombat.domConstructorErrorChecker(this,"WebSocket",arguments);var rwURL=url;return url!=null&&(rwURL=wombat.rewriteWSURL(url)),new WebSocket_(rwURL,configuration)}}(this.$wbwindow.WebSocket),this.$wbwindow.WebSocket.prototype=origWebSocket.prototype,Object.defineProperty(this.$wbwindow.WebSocket.prototype,"constructor",{value:this.$wbwindow.WebSocket}),addToStringTagToClass(this.$wbwindow.WebSocket,"WebSocket")}},Wombat.prototype.initElementGetSetAttributeOverride=function(){if(!this.wb_opts.skip_setAttribute&&this.$wbwindow.Element&&this.$wbwindow.Element.prototype){var wombat=this,ElementProto=this.$wbwindow.Element.prototype;if(ElementProto.setAttribute){var orig_setAttribute=ElementProto.setAttribute;ElementProto._orig_setAttribute=orig_setAttribute,ElementProto.setAttribute=function setAttribute(name,value){var rwValue=value;if(name&&typeof rwValue==="string"){var lowername=name.toLowerCase();if(this.tagName==="LINK"&&lowername==="href"&&rwValue.indexOf("data:text/css")===0)rwValue=wombat.rewriteInlineStyle(value);else if(lowername==="style")rwValue=wombat.rewriteStyle(value);else if(lowername==="srcset")rwValue=wombat.rewriteSrcset(value,this);else{var shouldRW=wombat.shouldRewriteAttr(this.tagName,lowername);shouldRW&&(wombat.removeWBOSRC(this),!this._no_rewrite&&(rwValue=wombat.rewriteUrl(value,false,wombat.rwModForElement(this,lowername))))}}return orig_setAttribute.call(this,name,rwValue)}}if(ElementProto.getAttribute){var orig_getAttribute=ElementProto.getAttribute;this.wb_getAttribute=orig_getAttribute,ElementProto.getAttribute=function getAttribute(name){var result=orig_getAttribute.call(this,name);if(result===null)return result;var lowerName=name;if(name&&(lowerName=name.toLowerCase()),wombat.shouldRewriteAttr(this.tagName,lowerName)){var maybeWBOSRC=wombat.retrieveWBOSRC(this);return maybeWBOSRC?maybeWBOSRC:wombat.extractOriginalURL(result)}return wombat.startsWith(lowerName,"data-")&&wombat.startsWithOneOf(result,wombat.wb_prefixes)?wombat.extractOriginalURL(result):result}}}},Wombat.prototype.initSvgImageOverrides=function(){if(this.$wbwindow.SVGImageElement){var svgImgProto=this.$wbwindow.SVGImageElement.prototype,orig_getAttr=svgImgProto.getAttribute,orig_getAttrNS=svgImgProto.getAttributeNS,orig_setAttr=svgImgProto.setAttribute,orig_setAttrNS=svgImgProto.setAttributeNS,wombat=this;svgImgProto.getAttribute=function getAttribute(name){var value=orig_getAttr.call(this,name);return name.indexOf("xlink:href")>=0||name==="href"?wombat.extractOriginalURL(value):value},svgImgProto.getAttributeNS=function getAttributeNS(ns,name){var value=orig_getAttrNS.call(this,ns,name);return name.indexOf("xlink:href")>=0||name==="href"?wombat.extractOriginalURL(value):value},svgImgProto.setAttribute=function setAttribute(name,value){var rwValue=value;return(name.indexOf("xlink:href")>=0||name==="href")&&(rwValue=wombat.rewriteUrl(value)),orig_setAttr.call(this,name,rwValue)},svgImgProto.setAttributeNS=function setAttributeNS(ns,name,value){var rwValue=value;return(name.indexOf("xlink:href")>=0||name==="href")&&(rwValue=wombat.rewriteUrl(value)),orig_setAttrNS.call(this,ns,name,rwValue)}}},Wombat.prototype.initCreateElementNSFix=function(){if(this.$wbwindow.document.createElementNS&&this.$wbwindow.Document.prototype.createElementNS){var orig_createElementNS=this.$wbwindow.document.createElementNS,wombat=this,createElementNS=function createElementNS(namespaceURI,qualifiedName){return orig_createElementNS.call(wombat.proxyToObj(this),wombat.extractOriginalURL(namespaceURI),qualifiedName)};this.$wbwindow.Document.prototype.createElementNS=createElementNS,this.$wbwindow.document.createElementNS=createElementNS}},Wombat.prototype.initInsertAdjacentElementHTMLOverrides=function(){var Element=this.$wbwindow.Element;if(Element&&Element.prototype){var elementProto=Element.prototype,rewriteFn=this.rewriteInsertAdjHTMLOrElemArgs;if(elementProto.insertAdjacentHTML){var origInsertAdjacentHTML=elementProto.insertAdjacentHTML;elementProto.insertAdjacentHTML=function insertAdjacentHTML(position,text){return rewriteFn(this,origInsertAdjacentHTML,position,text,true)}}if(elementProto.insertAdjacentElement){var origIAdjElem=elementProto.insertAdjacentElement;elementProto.insertAdjacentElement=function insertAdjacentElement(position,element){return rewriteFn(this,origIAdjElem,position,element,false)}}}},Wombat.prototype.initDomOverride=function(){var Node=this.$wbwindow.Node;if(Node&&Node.prototype){var rewriteFn=this.rewriteNodeFuncArgs;if(Node.prototype.appendChild){var originalAppendChild=Node.prototype.appendChild;Node.prototype.appendChild=function appendChild(newNode,oldNode){return rewriteFn(this,originalAppendChild,newNode,oldNode)}}if(Node.prototype.insertBefore){var originalInsertBefore=Node.prototype.insertBefore;Node.prototype.insertBefore=function insertBefore(newNode,oldNode){return rewriteFn(this,originalInsertBefore,newNode,oldNode)}}if(Node.prototype.replaceChild){var originalReplaceChild=Node.prototype.replaceChild;Node.prototype.replaceChild=function replaceChild(newNode,oldNode){return rewriteFn(this,originalReplaceChild,newNode,oldNode)}}this.overridePropToProxy(Node.prototype,"ownerDocument"),this.overridePropToProxy(this.$wbwindow.HTMLHtmlElement.prototype,"parentNode"),this.overridePropToProxy(this.$wbwindow.Event.prototype,"target")}this.$wbwindow.Element&&this.$wbwindow.Element.prototype&&(this.overrideParentNodeAppendPrepend(this.$wbwindow.Element),this.overrideChildNodeInterface(this.$wbwindow.Element,false)),this.$wbwindow.DocumentFragment&&this.$wbwindow.DocumentFragment.prototype&&this.overrideParentNodeAppendPrepend(this.$wbwindow.DocumentFragment)},Wombat.prototype.initDocOverrides=function($document){if(Object.defineProperty){this.overrideReferrer($document),this.defGetterProp($document,"origin",function origin(){return this.WB_wombat_location.origin}),this.defGetterProp(this.$wbwindow,"origin",function origin(){return this.WB_wombat_location.origin});var wombat=this,domain_setter=function domain(val){var loc=this.WB_wombat_location;loc&&wombat.endsWith(loc.hostname,val)&&(this.__wb_domain=val)},domain_getter=function domain(){return this.__wb_domain||this.WB_wombat_location.hostname};this.defProp($document,"domain",domain_setter,domain_getter)}},Wombat.prototype.initDocWriteOpenCloseOverride=function(){if(this.$wbwindow.DOMParser){var DocumentProto=this.$wbwindow.Document.prototype,$wbDocument=this.$wbwindow.document,docWriteWritelnRWFn=this.rewriteDocWriteWriteln,orig_doc_write=$wbDocument.write,new_write=function write(){return docWriteWritelnRWFn(this,orig_doc_write,arguments)};$wbDocument.write=new_write,DocumentProto.write=new_write;var orig_doc_writeln=$wbDocument.writeln,new_writeln=function writeln(){return docWriteWritelnRWFn(this,orig_doc_writeln,arguments)};$wbDocument.writeln=new_writeln,DocumentProto.writeln=new_writeln;var wombat=this,orig_doc_open=$wbDocument.open,new_open=function open(){var res,thisObj=wombat.proxyToObj(this);if(arguments.length===3){var rwUrl=wombat.rewriteUrl(arguments[0],false,"mp_");res=orig_doc_open.call(thisObj,rwUrl,arguments[1],arguments[2]),wombat.initNewWindowWombat(res,arguments[0])}else res=orig_doc_open.call(thisObj),wombat.initNewWindowWombat(thisObj.defaultView);return res};$wbDocument.open=new_open,DocumentProto.open=new_open;var originalClose=$wbDocument.close,newClose=function close(){var thisObj=wombat.proxyToObj(this);return wombat.initNewWindowWombat(thisObj.defaultView),originalClose.__WB_orig_apply?originalClose.__WB_orig_apply(thisObj,arguments):originalClose.apply(thisObj,arguments)};$wbDocument.close=newClose,DocumentProto.close=newClose;var oBodyGetter=this.getOrigGetter(DocumentProto,"body"),oBodySetter=this.getOrigSetter(DocumentProto,"body");oBodyGetter&&oBodySetter&&this.defProp(DocumentProto,"body",function body(newBody){return newBody&&(newBody instanceof HTMLBodyElement||newBody instanceof HTMLFrameSetElement)&&wombat.rewriteElemComplete(newBody),oBodySetter.call(wombat.proxyToObj(this),newBody)},oBodyGetter)}},Wombat.prototype.initIframeWombat=function(iframe){var win;win=iframe._get_contentWindow?iframe._get_contentWindow.call(iframe):iframe.contentWindow;try{if(!win||win===this.$wbwindow||win._skip_wombat||win._wb_wombat)return}catch(e){return}var src=iframe.src;this.initNewWindowWombat(win,src)},Wombat.prototype.initNewWindowWombat=function(win,src){var fullWombat=false;if(win&&!win._wb_wombat){if((!src||src===""||this.startsWithOneOf(src,["about:blank","javascript:"]))&&(fullWombat=true),!fullWombat&&this.wb_info.isSW){var origURL=this.extractOriginalURL(src);(origURL==="about:blank"||origURL.startsWith("srcdoc:")||origURL.startsWith("blob:"))&&(fullWombat=true)}if(fullWombat){var newInfo={};Object.assign(newInfo,this.wb_info);var wombat=new Wombat(win,newInfo);win._wb_wombat=wombat.wombatInit()}else this.initProtoPmOrigin(win),this.initPostMessageOverride(win),this.initMessageEventOverride(win),this.initCheckThisFunc(win)}},Wombat.prototype.initTimeoutIntervalOverrides=function(){var rewriteFn=this.rewriteSetTimeoutInterval;if(this.$wbwindow.setTimeout&&!this.$wbwindow.setTimeout.__$wbpatched$__){var originalSetTimeout=this.$wbwindow.setTimeout;this.$wbwindow.setTimeout=function setTimeout(){return rewriteFn(this,originalSetTimeout,arguments)},this.$wbwindow.setTimeout.__$wbpatched$__=true}if(this.$wbwindow.setInterval&&!this.$wbwindow.setInterval.__$wbpatched$__){var originalSetInterval=this.$wbwindow.setInterval;this.$wbwindow.setInterval=function setInterval(){return rewriteFn(this,originalSetInterval,arguments)},this.$wbwindow.setInterval.__$wbpatched$__=true}},Wombat.prototype.initWorkerOverrides=function(){var wombat=this;if(this.$wbwindow.Worker&&!this.$wbwindow.Worker._wb_worker_overriden){var orig_worker=this.$wbwindow.Worker;this.$wbwindow.Worker=function(Worker_){return function Worker(url,options){return wombat.domConstructorErrorChecker(this,"Worker",arguments),new Worker_(wombat.rewriteWorker(url),options)}}(orig_worker),this.$wbwindow.Worker.prototype=orig_worker.prototype,Object.defineProperty(this.$wbwindow.Worker.prototype,"constructor",{value:this.$wbwindow.Worker}),this.$wbwindow.Worker._wb_worker_overriden=true}if(this.$wbwindow.SharedWorker&&!this.$wbwindow.SharedWorker.__wb_sharedWorker_overriden){var oSharedWorker=this.$wbwindow.SharedWorker;this.$wbwindow.SharedWorker=function(SharedWorker_){return function SharedWorker(url,options){return wombat.domConstructorErrorChecker(this,"SharedWorker",arguments),new SharedWorker_(wombat.rewriteWorker(url),options)}}(oSharedWorker),this.$wbwindow.SharedWorker.prototype=oSharedWorker.prototype,Object.defineProperty(this.$wbwindow.SharedWorker.prototype,"constructor",{value:this.$wbwindow.SharedWorker}),this.$wbwindow.SharedWorker.__wb_sharedWorker_overriden=true}if(this.$wbwindow.ServiceWorkerContainer&&this.$wbwindow.ServiceWorkerContainer.prototype&&this.$wbwindow.ServiceWorkerContainer.prototype.register){var orig_register=this.$wbwindow.ServiceWorkerContainer.prototype.register;this.$wbwindow.ServiceWorkerContainer.prototype.register=function register(scriptURL,options){var newScriptURL=new URL(scriptURL,wombat.$wbwindow.document.baseURI).href,mod=wombat.getPageUnderModifier();return options&&options.scope?options.scope=wombat.rewriteUrl(options.scope,false,mod):options={scope:wombat.rewriteUrl("/",false,mod)},orig_register.call(this,wombat.rewriteUrl(newScriptURL,false,"sw_"),options)}}if(this.$wbwindow.Worklet&&this.$wbwindow.Worklet.prototype&&this.$wbwindow.Worklet.prototype.addModule&&!this.$wbwindow.Worklet.__wb_workerlet_overriden){var oAddModule=this.$wbwindow.Worklet.prototype.addModule;this.$wbwindow.Worklet.prototype.addModule=function addModule(moduleURL,options){var rwModuleURL=wombat.rewriteUrl(moduleURL,false,"js_");return oAddModule.call(this,rwModuleURL,options)},this.$wbwindow.Worklet.__wb_workerlet_overriden=true}},Wombat.prototype.initLocOverride=function(loc,oSetter,oGetter){if(Object.defineProperty)for(var prop,i=0;i=0&&props.splice(foundInx,1);return props}})}catch(e){console.log(e)}},Wombat.prototype.initHashChange=function(){if(this.$wbwindow.__WB_top_frame){var wombat=this,receive_hash_change=function receive_hash_change(event){if(event.data&&event.data.from_top){var message=event.data.message;message.wb_type&&(message.wb_type!=="outer_hashchange"||wombat.$wbwindow.location.hash==message.hash||(wombat.$wbwindow.location.hash=message.hash))}},send_hash_change=function send_hash_change(){var message={wb_type:"hashchange",hash:wombat.$wbwindow.location.hash};wombat.sendTopMessage(message)};this.$wbwindow.addEventListener("message",receive_hash_change),this.$wbwindow.addEventListener("hashchange",send_hash_change)}},Wombat.prototype.initPostMessageOverride=function($wbwindow){if($wbwindow.postMessage&&!$wbwindow.__orig_postMessage){var orig=$wbwindow.postMessage,wombat=this;$wbwindow.__orig_postMessage=orig;var postmessage_rewritten=function postMessage(message,targetOrigin,transfer,from_top){var from,src_id,this_obj=wombat.proxyToObj(this);if(this_obj.__WB_source&&this_obj.__WB_source.WB_wombat_location){var source=this_obj.__WB_source;if(from=source.WB_wombat_location.origin,this_obj.__WB_win_id||(this_obj.__WB_win_id={},this_obj.__WB_counter=0),!source.__WB_id){var id=this_obj.__WB_counter;source.__WB_id=id+source.WB_wombat_location.href,this_obj.__WB_counter+=1}this_obj.__WB_win_id[source.__WB_id]=source,src_id=source.__WB_id,this_obj.__WB_source=undefined}else from=window.WB_wombat_location.origin;var to_origin=targetOrigin;to_origin===this_obj.location.origin&&(to_origin=from);var new_message={from:from,to_origin:to_origin,src_id:src_id,message:message,from_top:from_top};if(targetOrigin!=="*"){if(this_obj.location.origin==="null"||this_obj.location.origin==="")return;targetOrigin=this_obj.location.origin}return orig.call(this_obj,new_message,targetOrigin,transfer)};$wbwindow.postMessage=postmessage_rewritten,$wbwindow.Window.prototype.postMessage=postmessage_rewritten;var eventTarget=null;eventTarget=$wbwindow.EventTarget&&$wbwindow.EventTarget.prototype?$wbwindow.EventTarget.prototype:$wbwindow;var _oAddEventListener=eventTarget.addEventListener;eventTarget.addEventListener=function addEventListener(type,listener,useCapture){var rwListener,obj=wombat.proxyToObj(this);if(type==="message"?rwListener=wombat.message_listeners.add_or_get(listener,function(){return wrapEventListener(listener,obj,wombat)}):type==="storage"?wombat.storage_listeners.add_or_get(listener,function(){return wrapSameOriginEventListener(listener,obj)}):rwListener=listener,rwListener)return _oAddEventListener.call(obj,type,rwListener,useCapture)};var _oRemoveEventListener=eventTarget.removeEventListener;eventTarget.removeEventListener=function removeEventListener(type,listener,useCapture){var rwListener,obj=wombat.proxyToObj(this);if(type==="message"?rwListener=wombat.message_listeners.remove(listener):type==="storage"?wombat.storage_listeners.remove(listener):rwListener=listener,rwListener)return _oRemoveEventListener.call(obj,type,rwListener,useCapture)};var override_on_prop=function(onevent,wrapperFN){var orig_setter=wombat.getOrigSetter($wbwindow,onevent),setter=function(value){this["__orig_"+onevent]=value;var obj=wombat.proxyToObj(this),listener=value?wrapperFN(value,obj,wombat):value;return orig_setter.call(obj,listener)},getter=function(){return this["__orig_"+onevent]};wombat.defProp($wbwindow,onevent,setter,getter)};override_on_prop("onmessage",wrapEventListener),override_on_prop("onstorage",wrapSameOriginEventListener)}},Wombat.prototype.initMessageEventOverride=function($wbwindow){!$wbwindow.MessageEvent||$wbwindow.MessageEvent.prototype.__extended||(this.addEventOverride("target"),this.addEventOverride("srcElement"),this.addEventOverride("currentTarget"),this.addEventOverride("eventPhase"),this.addEventOverride("path"),this.overridePropToProxy($wbwindow.MessageEvent.prototype,"source"),$wbwindow.MessageEvent.prototype.__extended=true)},Wombat.prototype.initUIEventsOverrides=function(){this.overrideAnUIEvent("UIEvent"),this.overrideAnUIEvent("MouseEvent"),this.overrideAnUIEvent("TouchEvent"),this.overrideAnUIEvent("FocusEvent"),this.overrideAnUIEvent("KeyboardEvent"),this.overrideAnUIEvent("WheelEvent"),this.overrideAnUIEvent("InputEvent"),this.overrideAnUIEvent("CompositionEvent")},Wombat.prototype.initOpenOverride=function(){var orig=this.$wbwindow.open;this.$wbwindow.Window.prototype.open&&(orig=this.$wbwindow.Window.prototype.open);var wombat=this,open_rewritten=function open(strUrl,strWindowName,strWindowFeatures){var rwStrUrl=wombat.rewriteUrl(strUrl,false,""),res=orig.call(wombat.proxyToObj(this),rwStrUrl,strWindowName,strWindowFeatures);return wombat.initNewWindowWombat(res,strUrl),res};this.$wbwindow.open=open_rewritten,this.$wbwindow.Window.prototype.open&&(this.$wbwindow.Window.prototype.open=open_rewritten);for(var i=0;i
+
+
+
+
+
+
+
+
+
+
+Chapter 1.2: Drawing Graphics
+
+
+
+
+
+
+
The Wayback Machine -
+http://web.archive.org/web/20220827033534/http://lameguy64.net/tutorials/pstutorials/chapter1/2-graphics.html
+
+
+
+
1.2. Drawing Graphics
+
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.
+
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.
+
Trivia: The easiest way to tell if a person is not a PS1 programmer
+is if they call the GTE the Geometry Transfer Engine instead of
+Transformation 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.
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.
+
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.
+
+
Ordering Tables
+
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 ClearOTagR()
+on an array of 32-bit ints.
+
The following figure visually describes the general structure of an ordering table.
+
+
Adding primitives to an ordering table is normally achieved using
+addPrim()/AddPrim() functions. DrawOTag() is used to begin
+processing of an ordering table.
+
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 ClearOTag() if you wish to use a non-reverse ordering table but the
+initialization speed won't be as fast as ClearOTagR() for larger tables
+as ClearOTagR() is DMA accelerated, but it can only generate reverse
+ordering tables.
+
For simplicity, a global ordering table array of 8 elements should suffice
+for this tutorial.
+
int ot[2][8];
+
+
The reason two arrays of ot are defined is for double buffered
+rendering reasons, which will be explained later in this tutorial.
+
+
Primitive Packets
+
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.
+
The following illustrates the typical structure of a primitive packet
+(the packet represents a SPRT primitive):
+
+
C implementation of the above structure
+
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;
+
+
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 Len field minus the
+tag word. The Next Pointer field is an address to the next primitive or
+return pointer to an ordering table it was sorted to.
+
A word in this context is a 4 byte integer.
+
Primitives are normally defined using primitive structures and macros
+defined in libgpu.h, or psxgpu.h in PSn00bSDK.
+
+
Preparing a Primitive
+
Primitive packets are normally prepared using primitive structures and
+macros. A solid color rectangular sprite (TILE) will be used for this
+tutorial.
+
Primitive preparation is as follows.
+
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
+
+
The setTile() 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.
+
The setXY0(), setWH() and setRGB0() 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.
+
Normally, primitive packets are defined to a primitive buffer.
+
+
Primitive Buffers
+
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.
+
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.
+
A primitive buffer can be defined as such, 32KB buffer for primitives
+should be plenty.
+
char primbuff[2][32768];
+char *nextpri;
+
+
The nextpri 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.
+
To prepare a primitive to the primitive buffer, simply cast the
+nextpri pointer into a primitive pointer.
+
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
+
+
After registering the primitive to the ordering table, you'll want to
+advance the nextpri pointer for another primitive to be written to.
+
nextpri += sizeof(TILE);
+
+
It is very important to advance this pointer, otherwise previously defined
+packets would get overwritten, corrupting the primitive buffer.
+
+
Sorting a Primitive to an Ordering Table
+
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.
+
Sorting a primitive to an ordering table is achieved using the
+addPrim() macro.
+
addPrim(ot[db], tile);
+
+
There's also a function version called AddPrim() but the macro version
+would be faster in the long run, mainly because code jumps are kept a minimum when
+using macros.
+
Taking the code snippet earlier, this is the process of how to create and sort
+a primitive to be drawn.
+
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
+
+
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.
+
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.
+
+
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.
+
+
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.
+
+
Drawing an Ordering Table
+
Ordering table processing is done using the DrawOTag() function.
+DrawOTag() 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.
+
Since this tutorial demonstrates the use of a reverse ordering table, the
+last element of the ordering table must be specified to DrawOTag(), as
+the chain starts at the last element and ends at the first.
+
DrawOTag(ot[db]+31);
+
+
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 DrawSync() before processing another
+ordering table.
+
+
Double Ordering Tables/Primitive Buffers
+
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.
+
+
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.
+
+
Sample Code
+
The sample code works off of the one from the previous example, but with
+graphics drawing code implemented.
+
#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;
+}
+
+
Compile and run the program and you should get a yellow square.
+Your very first graphic done with the GPU!
+
+
+
Conclusion
+
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.
+
A few things you may want to experiment with yourself for further
+learning:
+
+
Play around with the values specified in setXY0(), setRGB0()
+and setWH() to change the position, color and size of the sprite
+respectively.
+
Try drawing more sprites by repeating the primitive creation process.
+Make sure the nextpri and tile pointers have been advanced before
+creating a new primitive.
+
You can advance the primitive pointer with tile++;, and set the
+updated address to nextpri by converting types back
+(nextpri = (char*)tile;)
+
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.
+
+
The next tutorial will cover how to convert and upload texture data, as
+well as drawing said textures with sprite and polygon primitives.