Compare commits

..

3 Commits
master ... ce

Author SHA1 Message Date
duarteapcoelho eea8550238 CE: enable compression 2022-12-07 22:02:38 +00:00
duarteapcoelho ae6bbc5861 Fix ce/makefile missing 2022-12-07 21:40:07 +00:00
duarteapcoelho ffc5f9d651 Add untested TI-84 Plus CE support 2022-12-07 16:01:42 +00:00
18 changed files with 229 additions and 89 deletions

4
.gitignore vendored
View File

@ -7,6 +7,10 @@ sdl/*
gint/*
!gint/Makefile
ce/*
!ce/makefile
!ce/icon.png
resources/models/models.h
resources/models/models.blend1

View File

@ -8,10 +8,13 @@ prizm: prizm/racing.g3a
gint: gint/racing_singleplayer.g3a
ce: ce/bin/DEMO.8xp
clean:
make $(MFLAGS) -C sdl/ clean
make $(MFLAGS) -C prizm/ clean
make $(MFLAGS) -C gint/ clean
make $(MFLAGS) -C ce/ clean
sdl/racing: $(SOURCES)
make $(MFLAGS) -C sdl/
@ -22,6 +25,9 @@ prizm/racing.g3a: $(SOURCES)
gint/racing_singleplayer.g3a: $(SOURCES)
make $(MFLAGS) -C gint/
ce/bin/DEMO.8xp: $(SOURCES)
make $(MFLAGS) -C ce/
package: release/racing.zip release/racing.tar.gz release/racing_singleplayer.zip release/racing_singleplayer.tar.gz
release/racing.zip: prizm/racing.g3a

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)
- The multiplayer version runs at about 14 FPS and the singleplayer version runs at 24 FPS
- Runs at about 16 FPS normally and 21 FPS overclocked, on the fx-CG50.
## Controls
- Press `up`/`8` to accelerate and `down`/`5` to brake
@ -47,18 +47,21 @@ 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 split into two (one with a flat top and another with a flat bottom) and rasterized.
- The triangles are rasterized using the scan line algorithm with a depth buffer.
- 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.

BIN
ce/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

17
ce/makefile Normal file
View File

@ -0,0 +1,17 @@
# ----------------------------
# Makefile Options
# ----------------------------
NAME = DEMO
ICON = icon.png
DESCRIPTION = "CE C Toolchain Demo"
COMPRESSED = YES
ARCHIVED = NO
SRCDIR = ../src/
CFLAGS = -Wall -Wextra -Oz -DCE
CXXFLAGS = -Wall -Wextra -Oz -DCE
# ----------------------------
include $(shell cedev-config --makefile)

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 -lprof-cg
LDFLAGS = -m4-nofpu -mb -nostdlib -Wl,--no-warn-rwx-segments -T fxcg50.ld -lgint-cg -lc -lgcc -lgint-cg
INCLUDES =

36
src/display-ce.cpp Normal file
View File

@ -0,0 +1,36 @@
#ifdef CE
#include "display.h"
#include <ti/screen.h>
Color newColor(int r, int g, int b){
return {
.color = uint8_t((g >> 5) | ((b >> 6) << 3) | ((r >> 5) << 5))
};
}
namespace Display {
int textHeight = 0; // TODO
void init(){
gfx_Begin();
os_ClrHome();
gfx_SetDrawBuffer();
}
void clear(Color color){
fillRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, color);
}
void destroy(){
gfx_End();
}
void show(){
gfx_SwapDraw();
}
int textWidth(const char *text){
return 0; // TODO
}
void drawText(int x, int y, const char *text, Color color){
// TODO
}
};
#endif

21
src/display-ce.h Normal file
View File

@ -0,0 +1,21 @@
#include <graphx.h>
#define DISPLAY_WIDTH GFX_LCD_WIDTH
#define DISPLAY_HEIGHT GFX_LCD_HEIGHT
struct Color {
int r;
int g;
int b;
unsigned char color;
};
namespace Display {
inline void fillRect(int x, int y, int w, int h, Color color){
gfx_SetColor(color.color);
gfx_FillRectangle(x, y, w, h);
}
inline void drawPoint(int x, int y, Color color){
gfx_SetColor(color.color);
gfx_SetPixel(x, y);
}
};

28
src/input-ce.cpp Normal file
View File

@ -0,0 +1,28 @@
#ifdef CE
#include "input.h"
#include "util.h"
#include <keypadc.h>
namespace Input {
uint8_t lastData[7];
uint8_t data[7];
void init(){
}
void _updateKeys(){
kb_Scan();
memcpy(lastData, data, 7*sizeof(uint8_t));
for(int i = 0; i < 7; i++)
data[i] = kb_Data[i];
}
bool keyDown(int key){
if(key == -1)
return false;
return data[key / 8] & (1 << (key % 8));
}
bool keyDownLast(int key){
if(key == -1)
return false;
return lastData[key / 8] & (1 << (key % 8));
}
}
#endif

60
src/input-ce.h Normal file
View File

@ -0,0 +1,60 @@
#define KEY(x, y) (y-1)*8+x
#define KEY_MENU -1
#define KEY_EXIT -1
#define KEY_EXE KEY(0, 6)
#define KEY_AC -1
#define KEY_DEL -1
#define KEY_OPTN -1
#define KEY_VARS -1
#define KEY_0 KEY(0, 3)
#define KEY_1 KEY(1, 3)
#define KEY_2 KEY(1, 4)
#define KEY_3 KEY(1, 5)
#define KEY_4 KEY(2, 3)
#define KEY_5 KEY(2, 4)
#define KEY_6 KEY(2, 5)
#define KEY_7 KEY(3, 3)
#define KEY_8 KEY(3, 4)
#define KEY_9 KEY(3, 5)
#define KEY_A -1
#define KEY_B -1
#define KEY_C -1
#define KEY_D -1
#define KEY_E -1
#define KEY_F -1
#define KEY_G -1
#define KEY_H -1
#define KEY_I -1
#define KEY_J -1
#define KEY_K -1
#define KEY_L -1
#define KEY_M -1
#define KEY_N -1
#define KEY_O -1
#define KEY_P -1
#define KEY_Q -1
#define KEY_R -1
#define KEY_S -1
#define KEY_T -1
#define KEY_U -1
#define KEY_V -1
#define KEY_W -1
#define KEY_X -1
#define KEY_Y -1
#define KEY_Z -1
#define KEY_F1 -1
#define KEY_F2 -1
#define KEY_F3 -1
#define KEY_F4 -1
#define KEY_F5 -1
#define KEY_F6 -1
#define KEY_UP KEY(3, 7)
#define KEY_DOWN KEY(0, 7)
#define KEY_LEFT KEY(1, 7)
#define KEY_RIGHT KEY(2, 7)
#define KEY_SHIFT -1

View File

@ -28,13 +28,13 @@ vec3<float> cameraSpeed = {0, 0, 0};
float cameraAngle = 0;
#ifdef GINT
static GALIGNED(32) unsigned char depthBuffer[RENDER_WIDTH*RENDER_HEIGHT];
static GALIGNED(32) fp depthBuffer[RENDER_WIDTH*RENDER_HEIGHT];
#include "models.h"
#endif
int main(){
#ifndef GINT
unsigned char depthBuffer[RENDER_WIDTH*RENDER_HEIGHT];
fp depthBuffer[RENDER_WIDTH*RENDER_HEIGHT];
#include "models.h"
#endif
Rasterizer::depthBuffer = depthBuffer;
@ -62,12 +62,11 @@ int main(){
Track::coneMesh = {22, cone_triangles};
Track::simpleConeMesh = {2, simpleConeTriangles};
Time::init();
Display::init();
Display::clear(newColor(70, 180, 220));
Display::show();
Time::init();
Time::update();
Input::init();
@ -79,14 +78,14 @@ int main(){
{-1, -1, 0},
{-1, 1, 0},
{1, -1, 0},
{0, 0, -1},
{0, 1, 0},
newColor(255, 255, 0)
},
{
{-1, 1, 0},
{1, 1, 0},
{1, -1, 0},
{0, 0, -1},
{0, 1, 0},
newColor(255, 255, 0)
},
};
@ -139,7 +138,6 @@ 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);
@ -192,9 +190,6 @@ 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);
@ -205,14 +200,6 @@ 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];
unsigned char *depthBuffer;
fp *depthBuffer;
fp fov_d = 1;
@ -50,21 +50,14 @@ 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
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;
}
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));
#else
for(int i = 0; i < RENDER_WIDTH*RENDER_HEIGHT; i++){
depthBuffer[i] = value;
depthBuffer[i] = -1;
}
#endif
}
@ -105,7 +98,7 @@ namespace Rasterizer {
}
// Draws a triangle which has a horizontal top or bottom
inline void _drawFlatSideTriangle(vec3<int> points[3], unsigned char z, Color color, bool useDepth){
inline void _drawFlatSideTriangle(vec3<int> points[3], fp 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;
}
@ -148,7 +141,7 @@ namespace Rasterizer {
#endif
for(int x = minX; x <= maxX; x++){
if(z < depthBuffer[p] || !useDepth){
if(z < depthBuffer[p] || depthBuffer[p] == fp(-1) || !useDepth){
if(useDepth){
depthBuffer[p] = z;
}
@ -184,7 +177,11 @@ namespace Rasterizer {
toScreen(p2_d),
};
unsigned char z = (points[0].z + points[1].z + points[2].z) / 3;
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;
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);
@ -356,10 +353,6 @@ 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;
@ -369,14 +362,12 @@ namespace Rasterizer {
}
int inside = 5;
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;
}
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;
}
}
@ -398,28 +389,8 @@ 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,7 +26,6 @@ struct Mesh {
};
class Model {
fp radius;
public:
Mesh mesh;
mat4 modelMatrix;
@ -39,7 +38,7 @@ public:
namespace Rasterizer {
void init();
void reset();
extern unsigned char *depthBuffer;
extern fp *depthBuffer;
extern fp fov_d;
void setFOV(int fov);

15
src/time-ce.cpp Normal file
View File

@ -0,0 +1,15 @@
#ifdef CE
#include "time.h"
#include <sys/rtc.h>
namespace Time {
void init(){
rtc_Enable(0);
}
void update(){
const float lastTime = time;
time = rtc_Time();
delta = time - lastTime;
}
};
#endif

View File

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

View File

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

View File

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

View File

@ -20,6 +20,12 @@
#define srand sys_srand
#endif
#ifdef CE
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#endif
#ifdef SDL
#include "stdio.h"
#include "stdlib.h"