Using the Playstation 2 Linux development kit, we created a number of projects, culminating in a 3D program. The aim was to create a program that would use the available hardware of the Playstation 2 in a suitable manner, taking into account the powerful parallel rendering architecture.
My application (named "GunnAPI") is an extension of the application framework by Dr. H. Fortuna. The API is expanded in two areas: lighting and sound. Expanding on the ambient and directional lights in the initial framework, GunnAPI supports point lights. This means that the framework can render 3D scenes with up to 7 different lights in total. It also integrates the Linux OpenSound application framework to provide very basic audio support for games.
Three point lights (green, purple and blue) with test model.
Another three point lights, where the blue light is clearly illuminating two models.
The full source code, including makefile, is available HERE, however the main changes to support point lights (and the bulk of the project work) are found in the VU code:
/*
;* PS2 Application Framework
;* University of Abertay Dundee
;* May be used for educational purposed only
;* Author - Dr Henry S Fortuna
;* Modified by Shaun Pattillo
;* Revision: 1.7 (Specular Branch)
;* Date: 25/11/2007
;*/
; The static (or initialisation buffer) i.e. stuff that doesn't change for each
; time this code is called.
Scales .assign 0 ; 0 | Scale | Vector
LightDirs .assign 1 ; 1-4 | Directional Light Directions | 4x4 Matrix
LightCols .assign 5 ; 5-8 | Directional/Ambient Colour | 4x4 Matrix
Transform .assign 9 ; 9-12 | Transform | 4x4 Matrix
LightTrans .assign 13 ; 13-16 | World Transform | 4x4 Matrix
PointLPos .assign 17 ; 17-20 | Point Light Positions | 4x4 Matrix
PointLCols .assign 21 ; 21-24 | Point Light Colours | Light4x4 Matrix
CameraPos .assign 25 ; 25 | Camera Position | Vector
; The input buffer (relative the the start of one of the double buffers)
NumVerts .assign 0
GifPacket .assign 1
UVStart .assign 2
NormStart .assign 3
VertStart .assign 4
; The output buffer (relative the the start of one of the double buffers)
GifPacketOut .assign 248
UVStartOut .assign 249
NormStartOut .assign 250
VertStartOut .assign 251
; Note that we have 4 data buffers: InputBuffer0, OutputBuffer0, InputBuffer1, and OutputBuffer1.
; Each buffer is 248 quad words. The different buffers are selected by reading the current offset
; from xtop (which is swapped automatically by the PS2 after each MSCALL / MSCNT).
.include "vcl_sml.i"
.init_vf_all
.init_vi_all
.syntax new
.vu
--enter
--endenter
; The START or Init code, that is only called once per frame.
START:
fcset 0x000000
lq fScales, Scales(vi00)
lq.xyz fLightDirs[0], LightDirs+0(vi00) ; Load the light directions
lq.xyz fLightDirs[1], LightDirs+1(vi00)
lq.xyz fLightDirs[2], LightDirs+2(vi00)
lq.xyz fLightCols[0], LightCols+0(vi00) ; Load the light colours
lq.xyz fLightCols[1], LightCols+1(vi00)
lq.xyz fLightCols[2], LightCols+2(vi00)
lq.xyz fAmbient, LightCols+3(vi00)
MatrixLoad fTransform, Transform, vi00
MatrixLoad fLightTrans, LightTrans, vi00
MatrixLoad fPointLPos, PointLPos, vi00
lq fCameraPos, CameraPos(vi00) ; Load the current camera position
; This begin code is called once per batch
begin:
xtop iDBOffset ; Load the address of the current buffer (will either be QW 16 or QW 520)
ilw.x iNumVerts, NumVerts(iDBOffset)
iadd iNumVerts, iNumVerts, iDBOffset
iadd Counter, vi00, iDBOffset
loop:
; ***** VERTEX TRANSFORM *****
lq fVert, VertStart(Counter) ; Load the vertex from the input buffer
MatrixMultiplyVertex Vert, fTransform, fVert ; Transform the vertex into world(clip) space
clipw.xyz Vert, Vert ; Clip it
fcand vi01, 0x3FFFF
iaddiu iADC, vi01, 0x7FFF
ilw.w iNoDraw, UVStart(Counter) ; Load the iNoDraw flag. If true we should set the
iadd iADC, iADC, iNoDraw ; ADC bit so the vert isn't drawn
isw.w iADC, VertStartOut(Counter)
div q, vf00[w], Vert[w]
; ***** TEXTURE MAPPING *****
lq UV, UVStart(Counter) ; Handle the tex-coords
mul UV, UV, q
sq UV, UVStartOut(Counter)
mul.xyz Vert, Vert, q ; Scale the final vertex to fit to the screen.
mula.xyz acc, fScales, vf00[w]
madd.xyz Vert, Vert, fScales
ftoi4.xyz Vert, Vert
sq.xyz Vert, VertStartOut(Counter) ; And store in the output buffer
; ***** LIGHTING *****
MatrixMultiplyVertex fVert, fLightTrans, fVert ; Transform the vertex into world space
lq Norm, NormStart(Counter) ; Load the normal
MatrixMultiplyVertex Norm, fLightTrans, Norm ; Transform the normal into world space (Important that norm.w is 0)
; ***** POINT LIGHTS *****
sub.xyz ToLight[0], fPointLPos[0], fVert ; Get the vector from the vert to the point lights
sub.xyz ToLight[1], fPointLPos[1], fVert
sub.xyz ToLight[2], fPointLPos[2], fVert
VectorNormalizeXYZ ToLight[0], ToLight[0] ; Normalise this vector
VectorNormalizeXYZ ToLight[1], ToLight[1] ; (and it becomes the light direction, just as in directional lighting)
VectorNormalizeXYZ ToLight[2], ToLight[2]
VectorDotProduct fPointIntensity[0], ToLight[0], Norm ; Calcualate the intensity based on normals.
VectorDotProduct fPointIntensity[1], ToLight[1], Norm
VectorDotProduct fPointIntensity[2], ToLight[2], Norm
; ***** SPECULAR *****
lq fCameraPos, CameraPos(vi00) ; Load the current camera position
MatrixMultiplyVertex fCameraPos, fLightTrans, fCameraPos
sub.xyz fToCam, fCameraPos, fVert ; Get the vector from vertex to camera
VectorNormalizeXYZ fToCam, fToCam ; And normalize it
add.xyz HalfVec[0], fToCam, ToLight[0]
add.xyz HalfVec[1], fToCam, ToLight[1]
add.xyz HalfVec[2], fToCam, ToLight[2]
VectorNormalizeXYZ HalfVec[0], HalfVec[0]
VectorNormalizeXYZ HalfVec[1], HalfVec[1]
VectorNormalizeXYZ HalfVec[2], HalfVec[2]
VectorDotProduct fSpecIntensity[0], HalfVec[0], Norm
VectorDotProduct fSpecIntensity[1], HalfVec[1], Norm
VectorDotProduct fSpecIntensity[2], HalfVec[2], Norm
POWER32 fSpecIntensity, fPointIntensity ;macro'd (power 32 fSpec that add it to fPoint)
; ***** POINT LIGHTS *****
max.x fPointIntensity[0], fPointIntensity[0], vf00 ; Clamp to > 0
mini.x fPointIntensity[0], fPointIntensity[0], vf00[w] ; Clamp to < 1
max.x fPointIntensity[1], fPointIntensity[1], vf00 ; Clamp to > 0
mini.x fPointIntensity[1], fPointIntensity[1], vf00[w] ; Clamp to < 1
max.x fPointIntensity[2], fPointIntensity[2], vf00 ; Clamp to > 0
mini.x fPointIntensity[2], fPointIntensity[2], vf00[w] ; Clamp to < 1
MatrixLoad fPointLCols, PointLCols, vi00 ; Load the light colour
mula.xyz acc, fPointLCols[0], fPointIntensity[0][x] ; Scale the colour by the intensity
madda.xyz acc, fPointLCols[1], fPointIntensity[1][x]
madd.xyz fPointCols, fPointLCols[2], fPointIntensity[2][x]
loi 128 ; Load 128 to I
addi.w fPointCols, vf00, i
; ***** DIRECTIONAL LIGHTS *****
mula.xyz acc, fLightDirs[0], Norm[x] ; "Transform" the normal by the light direction matrix
madd.xyz acc, fLightDirs[1], Norm[y] ; This has the effect of outputting a vector with all
madd.xyz fDirIntensities, fLightDirs[2], Norm[z] ; four intensities, one for each light.
mini.xyz fDirIntensities, fDirIntensities, vf00[w] ; Clamp the intensity to 0..1
max.xyz fDirIntensities, fDirIntensities, vf00[x]
mula.xyz acc, fLightCols[0], fDirIntensities[x] ; Transform the intensities by the light colour matrix
madda.xyz acc, fLightCols[1], fDirIntensities[y] ; This gives the final total directional light colour
madda.xyz acc, fLightCols[2], fDirIntensities[z]
madd.xyz fDirCols, fAmbient, vf00[w]
loi 128 ; Load 128 to I
addi.w fDirCols, vf00, i
; ***** AVERAGING OUT LIGHTING FOR VERT *****
add fFinalLight, fDirCols, fPointCols
; ***** CONVERT AND UPLOAD FINAL LIGHT DATA *****
ftoi0 iFinalLight, fFinalLight ; Convert to ints
sq iFinalLight, NormStartOut(Counter) ; And write to the output buffer
; ***** LOOP COUNTER/RENDER KICK *****
iaddiu Counter, Counter, 3
ibne Counter, iNumVerts, loop ; Loop until all of the verts in this batch are done.
iaddiu iKick, iDBOffset, GifPacketOut
lq GP, GifPacket(iDBOffset) ; Copy the GIFTag to the output buffer
sq GP, GifPacketOut(iDBOffset)
xgkick iKick ; and render!
--cont
; --cont is like end, but it really means pause, as this is where the code
; will pick up from when MSCNT is called.
b begin ; Which will make it hit this code which takes it back to the start, but
; skips the initialisation which we don't want done twice.
--exit
--endexit