Compare commits

...

7 Commits
v1.1 ... master

Author SHA1 Message Date
duarteapcoelho ca63ef88ea Fix freezing because of multiplayer 2022-12-10 16:52:15 +00:00
duarteapcoelho f18108eaee Update README 2022-12-08 20:46:49 +00:00
duarteapcoelho df09c12c07 Clip models 2022-12-08 20:46:42 +00:00
duarteapcoelho 3f1ea2cc5d Fix sun not being visible 2022-12-08 18:00:41 +00:00
duarteapcoelho 98f79eeb05 Do backface culling before anything else 2022-12-08 17:06:40 +00:00
duarteapcoelho 332771104f gint: Add libprof for more accurate FPS 2022-12-08 16:20:43 +00:00
duarteapcoelho 136e536544 Make depth buffer 8 bit 2022-12-08 15:44:53 +00:00
9 changed files with 90 additions and 33 deletions

View File

@ -8,7 +8,7 @@ A 3D, multiplayer racing game for casio fx-CG50 calculators
## Features
- 3D graphics
- Simple multiplayer (just connect two calculators)
- Runs at about 16 FPS normally and 21 FPS overclocked, on the fx-CG50.
- The multiplayer version runs at about 14 FPS and the singleplayer version runs at 24 FPS
## Controls
- Press `up`/`8` to accelerate and `down`/`5` to brake
@ -47,21 +47,18 @@ This version supports multiplayer, but it's slower than the gint version.
This version doesn't support multiplayer, but it runs faster and doesn't have a border.
#### Linux
- Install gint ([https://gitea.planet-casio.com/Lephenixnoir/gint](https://gitea.planet-casio.com/Lephenixnoir/gint))
- Install libprof ([https://gitea.planet-casio.com/Lephenixnoir/libprof](https://gitea.planet-casio.com/Lephenixnoir/libprof))
- Run `make gint`
## Technical information
### 3D rendering
- All the rendering code is in `src/rasterizer.h` and `src/rasterizer.cpp`
- Every triangle is clipped to avoid drawing triangles outside the screen. If a triangle is only partially inside the screen, it's cut in one or two triangles. This doesn't happen with the cones and the car to improve performance.
- The triangles are rasterized using the scan line algorithm with a depth buffer.
- The triangles are split into two (one with a flat top and another with a flat bottom) and rasterized.
- This renderer only supports diffuse directional lighting, because this way there is only one color per triangle, which increases performance.
- Because the calculator doesn't have a floating point unit (FPU), everything related to rendering uses fixed point numbers (defined in src/fp.h). This caused some issues related to precision, most of which were solved by checking where the floating point calculations were overflowing.
- To improve performance, the cones that are too far away from the camera are replaced with a simpler model and the ones even further away aren't drawn at all.
#### Potential rendering performance improvements
- Use DMA to clear the screen and draw the grass (in progress)
- Clip models before clipping triangles
### Multiplayer
All of the multiplayer code is in `src/main.cpp`
- When the game starts, a second car is created outside the track.

View File

@ -1,7 +1,7 @@
CC = sh-elf-g++
CFLAGS += -Wall -Wextra -Ofast -funroll-loops -DGINT
CFLAGS += -DFXCG50 -DTARGET_FXCG50 -m4-nofpu -mb -ffreestanding -nostdlib -fstrict-volatile-bitfields
LDFLAGS = -m4-nofpu -mb -nostdlib -Wl,--no-warn-rwx-segments -T fxcg50.ld -lgint-cg -lc -lgcc -lgint-cg
LDFLAGS = -m4-nofpu -mb -nostdlib -Wl,--no-warn-rwx-segments -T fxcg50.ld -lgint-cg -lc -lgcc -lgint-cg -lprof-cg
INCLUDES =

View File

@ -28,13 +28,13 @@ vec3<float> cameraSpeed = {0, 0, 0};
float cameraAngle = 0;
#ifdef GINT
static GALIGNED(32) fp depthBuffer[RENDER_WIDTH*RENDER_HEIGHT];
static GALIGNED(32) unsigned char depthBuffer[RENDER_WIDTH*RENDER_HEIGHT];
#include "models.h"
#endif
int main(){
#ifndef GINT
fp depthBuffer[RENDER_WIDTH*RENDER_HEIGHT];
unsigned char depthBuffer[RENDER_WIDTH*RENDER_HEIGHT];
#include "models.h"
#endif
Rasterizer::depthBuffer = depthBuffer;
@ -62,6 +62,8 @@ int main(){
Track::coneMesh = {22, cone_triangles};
Track::simpleConeMesh = {2, simpleConeTriangles};
Time::init();
Display::init();
Display::clear(newColor(70, 180, 220));
Display::show();
@ -77,14 +79,14 @@ int main(){
{-1, -1, 0},
{-1, 1, 0},
{1, -1, 0},
{0, 1, 0},
{0, 0, -1},
newColor(255, 255, 0)
},
{
{-1, 1, 0},
{1, 1, 0},
{1, -1, 0},
{0, 1, 0},
{0, 0, -1},
newColor(255, 255, 0)
},
};
@ -137,6 +139,7 @@ int main(){
#endif
#ifdef PRIZM
Serial_Close(1);
while(Serial_IsOpen() != 1){
unsigned char mode[6] = {0, 5, 0, 0, 0, 0}; // 9600 bps 8n1
Serial_Open(mode);
@ -189,6 +192,9 @@ int main(){
#ifdef PRIZM
while(Input::keyDown(KEY_MENU))
Input::update();
Serial_Close(1);
timer = Timer_Install(0, []() {
Keyboard_PutKeycode(4, 9, 0);
Timer_Stop(timer);
@ -199,6 +205,14 @@ int main(){
Bdisp_EnableColor(1);
GetKey(&k);
Serial_Close(1);
while(Serial_IsOpen() != 1){
unsigned char mode[6] = {0, 5, 0, 0, 0, 0}; // 9600 bps 8n1
Serial_Open(mode);
}
Serial_ClearTX();
Serial_ClearRX();
continue;
#endif
#ifdef SDL

View File

@ -37,7 +37,7 @@ inline int max(int a, int b){
namespace Rasterizer {
Plane clippingPlanes[5];
fp *depthBuffer;
unsigned char *depthBuffer;
fp fov_d = 1;
@ -50,14 +50,21 @@ namespace Rasterizer {
}
void reset(){
unsigned char value = -1;
#if GINT || PRIZM
long v = value | (value << 8) | (value << 16) | (value << 24);
#endif
#if GINT && PIXEL_SIZE == 1
fp v = -1;
fp *depthBuffer_P1 = (fp*) mmu_translate_uram(depthBuffer);
cache_ocbp(depthBuffer, RENDER_WIDTH*RENDER_HEIGHT*sizeof(fp));
dma_memset(depthBuffer_P1, *((uint32_t*)&v), RENDER_WIDTH*RENDER_HEIGHT*sizeof(fp));
unsigned char *depthBuffer_P1 = (unsigned char*) mmu_translate_uram(depthBuffer);
cache_ocbp(depthBuffer, RENDER_WIDTH*RENDER_HEIGHT*sizeof(unsigned char));
dma_memset(depthBuffer_P1, *((uint32_t*)&v), RENDER_WIDTH*RENDER_HEIGHT*sizeof(unsigned char));
#elif PRIZM
for(int i = 0; i < RENDER_WIDTH*RENDER_HEIGHT/4; i++){
*(((long*)depthBuffer) + i) = v;
}
#else
for(int i = 0; i < RENDER_WIDTH*RENDER_HEIGHT; i++){
depthBuffer[i] = -1;
depthBuffer[i] = value;
}
#endif
}
@ -98,7 +105,7 @@ namespace Rasterizer {
}
// Draws a triangle which has a horizontal top or bottom
inline void _drawFlatSideTriangle(vec3<int> points[3], fp z, Color color, bool useDepth){
inline void _drawFlatSideTriangle(vec3<int> points[3], unsigned char z, Color color, bool useDepth){
if(points[0].y == points[1].y && points[1].y == points[2].y && points[2].y == points[0].y){
return;
}
@ -141,7 +148,7 @@ namespace Rasterizer {
#endif
for(int x = minX; x <= maxX; x++){
if(z < depthBuffer[p] || depthBuffer[p] == fp(-1) || !useDepth){
if(z < depthBuffer[p] || !useDepth){
if(useDepth){
depthBuffer[p] = z;
}
@ -177,11 +184,7 @@ namespace Rasterizer {
toScreen(p2_d),
};
if(dot(mat4::toMat3(model->viewMatrix) * mat4::toMat3(model->modelMatrix) * triangle.normal, vec3<fp>(0, 0, 1)) > 0){
return;
}
fp z = (points[0].z + points[1].z + points[2].z) / 3;
unsigned char z = (points[0].z + points[1].z + points[2].z) / 3;
if(isShaded){
fp brightness = dot(mat4::toMat3(model->modelMatrix) * triangle.normal, vec3<fp>(I_SQRT_3, -I_SQRT_3, -I_SQRT_3)) * fp(0.6) + fp(0.4);
@ -353,6 +356,10 @@ namespace Rasterizer {
}
inline void drawTriangle(Model *model, Triangle triangle, bool useDepth, bool isShaded, bool clipTriangles){
if(dot(mat4::toMat3(model->viewMatrix) * mat4::toMat3(model->modelMatrix) * triangle.normal, vec3<fp>(0, 0, 1)) > 0){
return;
}
triangle.p0 = model->viewMatrix * model->modelMatrix * triangle.p0;
triangle.p1 = model->viewMatrix * model->modelMatrix * triangle.p1;
triangle.p2 = model->viewMatrix * model->modelMatrix * triangle.p2;
@ -362,12 +369,14 @@ namespace Rasterizer {
}
int inside = 5;
for(int i = 0; i < 5; i++){
if(dot(clippingPlanes[i].n, triangle.p0) + clippingPlanes[i].d < 0
|| dot(clippingPlanes[i].n, triangle.p1) + clippingPlanes[i].d < 0
|| dot(clippingPlanes[i].n, triangle.p2) + clippingPlanes[i].d < 0){
inside--;
break;
if(clipTriangles){
for(int i = 0; i < 5; i++){
if(dot(clippingPlanes[i].n, triangle.p0) + clippingPlanes[i].d < 0
|| dot(clippingPlanes[i].n, triangle.p1) + clippingPlanes[i].d < 0
|| dot(clippingPlanes[i].n, triangle.p2) + clippingPlanes[i].d < 0){
inside--;
break;
}
}
}
@ -389,8 +398,28 @@ Model::Model(){
}
Model::Model(Mesh mesh){
this->mesh = mesh;
fp radius2 = 0;
for(int i = 0; i < mesh.numTriangles; i++){
fp d0 = mesh.triangles[i].p0.length2();
fp d1 = mesh.triangles[i].p1.length2();
fp d2 = mesh.triangles[i].p2.length2();
radius2 = max(radius2, d0);
radius2 = max(radius2, d1);
radius2 = max(radius2, d2);
}
float i_radius = _isqrt(radius2);
radius = 1.0f/i_radius;
}
void Model::draw(bool useDepth, bool isShaded, bool clipTriangles){
if(!clipTriangles){
vec3<fp> center = viewMatrix * modelMatrix * vec3<fp>(0, 0, 0);
for(int i = 0; i < 5; i++){
if(dot(Rasterizer::clippingPlanes[i].n, center) + Rasterizer::clippingPlanes[i].d < radius){
return;
}
}
}
for(int i = 0; i < mesh.numTriangles; i++){
Rasterizer::drawTriangle(this, mesh.triangles[i], useDepth, isShaded, clipTriangles);
}

View File

@ -26,6 +26,7 @@ struct Mesh {
};
class Model {
fp radius;
public:
Mesh mesh;
mat4 modelMatrix;
@ -38,7 +39,7 @@ public:
namespace Rasterizer {
void init();
void reset();
extern fp *depthBuffer;
extern unsigned char *depthBuffer;
extern fp fov_d;
void setFOV(int fov);

View File

@ -1,12 +1,23 @@
#ifdef GINT
#include "time.h"
#include <gint/rtc.h>
#include <libprof.h>
namespace Time {
void init(){}
prof_t prof;
void init(){
prof_init();
prof = prof_make();
}
void update(){
prof_leave(prof);
const float lastTime = time;
time = rtc_ticks();
delta = time - lastTime;
delta = prof_time(prof) / 1000.0f / (1000.0f / 128.0f);
prof = prof_make();
prof_enter(prof);
}
};
#endif

View File

@ -3,6 +3,8 @@
#include <fxcg/rtc.h>
namespace Time {
void init(){
}
void update(){
const float lastTime = time;
time = RTC_GetTicks();

View File

@ -3,6 +3,8 @@
#include <SDL2/SDL.h>
namespace Time {
void init(){
}
void update(){
const float lastTime = time;
time = ((float)(SDL_GetTicks()) / (1000.0/128.0));

View File

@ -3,5 +3,6 @@
namespace Time {
extern float time;
extern float delta;
void init();
void update();
};