Optimize rendering

This commit is contained in:
Miloslav Ciz 2025-04-24 16:55:35 +02:00
parent c22f17ef8c
commit 5265552c57
8 changed files with 117 additions and 89 deletions

View file

@ -1,7 +1,9 @@
fuck issue trackers :D
=========== GENERAL ==============
- make some kinda repo for world record runs?
- fix the ramp map again due to new physics
- should drifting make a sound?
- keyboard ghosting is an issue, particularly when initiating drift with brake
(arrow keys must be used with S for braking for left drift to work) -- think
about what to do about this? or leave as is?
@ -11,46 +13,11 @@
its velocity by a proportion of car's velocity change (this minus prev.
frame), then offset car body by this. However we'll also have to transform
inbetween world space and model space.
- Consider better input handling in SDL? Currently it just detects presses on
the exact frame, so a press can be missed. But how tho?
- try to add distance fog?
- use 332 in SDL with potato?
- option to turn on simple 332 colors?
- c99 may impose limit 4095 chars on str literal, gives warning on internal
data file, try to somehow hack around it (maybe just convert it to an array in
the end?) Maybe this: make a standalone C file with the string in it that
when compiled and run outputs the array.
- culling is very slow now, it showed that distance bailout can accelerate it
a lot, try to make a more accurate bailaout and see if it's faster (watch
out for bugs due to false positives)
- maps to make:
- there should be these maps:
- compiled in:
- standard maps, let's say 5?
- additionally a few (5?) extremely simple maps, compile time option to
only include these, for very weak devices with limited block/triangle
count
- bonus maps in the external file (also 5?)
- map types:
- purely falling map DONE
- traveling salesman kind of maze, with fans n shit KINDA DONE
- city map? DONE
- some kinda buggy bumpy downhill DONE
- map where car starts upside down DONE
- dirt map DONE
- ice map
- boss map DONE
- text or picture from blocks? DONE
- chessboard from different materials KINDA DONE
- falling tube map? DONE
- grass hills as decoration? DONE
- something with multiple roads DONE
- RPG kinda map? could be in bonus maps KINDA DONE
- some speed map (mostly flat to prevent high speed bugs) DONE
- some (at least partially) interior map KINDA DONE
- something with multiple finishes DONE
- U-ramp to build speed and jump up to catch a CP (done) DONE
- jump through air ring with CP DONE
- replay validation?
- final tests:
- very long replay
@ -66,6 +33,8 @@
- correct saving or replays etc
- empty and large data file
- FPS on each platform
- try to use the racing module by itself
- doxygen documentation
=========== BUGS =================
@ -78,7 +47,16 @@
=========== HANDLED ==============
- should drifting make a sound? NO NEED
- Consider better input handling in SDL? Currently it just detects presses on
the exact frame, so a press can be missed. But how tho? LOOKS FINE, it's
only an issue with extremely low FPS, at which point it's unplayable anyway
- use 332 in SDL with potato?
- option to turn on simple 332 colors?
- MAP4: one triangle in top section is missing!
- culling is very slow now, it showed that distance bailout can accelerate it
a lot, try to make a more accurate bailaout and see if it's faster (watch
out for bugs due to false positives) PROLLY FINE
- when non-rotating, the car is very fast, allowing uberbugs: find out why and
fix
- allow slowing down in air like in TM?
@ -253,6 +231,34 @@
- camera behavior? what if car is riding upside down (on magnet) etc? <- looks cool now
- allow car to be flipped upside down on start? with start block transform
- track size: 64x64x64
- maps to make:
- there should be these maps:
- compiled in:
- standard maps, let's say 5?
- additionally a few (5?) extremely simple maps, compile time option to
only include these, for very weak devices with limited block/triangle
count
- bonus maps in the external file (also 5?)
- map types:
- purely falling map DONE
- traveling salesman kind of maze, with fans n shit KINDA DONE
- city map? DONE
- some kinda buggy bumpy downhill DONE
- map where car starts upside down DONE
- dirt map DONE
- ice map
- boss map DONE
- text or picture from blocks? DONE
- chessboard from different materials KINDA DONE
- falling tube map? DONE
- grass hills as decoration? DONE
- something with multiple roads DONE
- RPG kinda map? could be in bonus maps KINDA DONE
- some speed map (mostly flat to prevent high speed bugs) DONE
- some (at least partially) interior map KINDA DONE
- something with multiple finishes DONE
- U-ramp to build speed and jump up to catch a CP (done) DONE
- jump through air ring with CP DONE
- sky images could be just composed of 4x4 normal images? then we only need
one type of image
TOTAL SIZE OF TEXTURES:

View file

@ -448,7 +448,7 @@ static const char *LCR_internalDataFile =
":^D1bJ:f11i:^J1bL:f11i"
":^D0bJ-:f11i:^J0bL-:f11i"
":^E1s:f511"
":>E0k:f513"
":>E0k:f514"
// slope:
":^C2vI-:f611"
":^p3v:fj11"
@ -7066,9 +7066,9 @@ void LCR_imageChangeBrightness(int up)
Samples currently loaded image at given pixels coordinates (with wrapping).
This is slower than reading the image pixel by pixel.
*/
uint16_t LCR_sampleImage(int x, int y)
uint16_t LCR_sampleImage(int_fast32_t x, int_fast32_t y)
{
// TODO: bottleneck, later on optimize here
// bottleneck here, optimization will increase rendering performance
x = (y % LCR_IMAGE_SIZE) * LCR_IMAGE_SIZE + (x % LCR_IMAGE_SIZE);
x += (x < 0) * (LCR_IMAGE_SIZE * LCR_IMAGE_SIZE);
return LCR_currentImage.palette[LCR_currentImage.image[x]];

5
data
View file

@ -151,3 +151,8 @@ details
#BLCbonus2;
#RLCtiny2;00LCtiny2;833ee4b2 0000262:0014:0a10:0031:00d0:0074
#RLCtiny4;00LCtiny4;1787f12a 0000233:0121:0083:0091:0073:0061:0043:0031:0033:0041:0033:0061:00a3:0142:01b1:0129:0061:0159:0031:00f9:0051
#RLCtiny1;00LCtiny1;ae1ab677 0000327:0011:0159:0031:00d3:0041:0039:0051:0113:0021:0099:0041:0129:00d1:0153:0041:0073:0051:0043:0041:0023:0071:0023:0021:0055:0031:0013:00c1:0033:01a1:00c9:0031:01a5:0031:01b3:0041:00e9:0031:0043:0041:0049
#RLC1;00LC1;8bd6e314 0000566:0011:0169:0031:0109:0081:0039:0051:0039:0051:0039:0051:00b9:0011:0069:0031:0119:0091:0025:0041:0083:0132:0033:0361:00d9:0101:00d9:0081:0269:0021:00a9:0021:01a5:0031:01f3:0031:0103:0031:0023:00b1:0033:00c1:0023:0071:0063:0021:0053:0071:0189:0021
#BLC1;
#RLC1;00LC1;8bd6e314 0000527:0011:0149:0041:0039:0031:00b9:0031:0079:00a1:02d9:0081:0035:0031:0049:0271:0073:00a1:0043:0041:0099:0061:00a9:0091:0119:0031:0063:0041:0079:0031:0145:0041:0053:0171:00d3:0041:0083:0041:0043:00e1:0023:00a1:01c9:0081:0083:0051:0099:0011:0399:0031:00f3:0031:0069:0031
#RLC1;00LC1;8bd6e314 0000523:0011:0139:0031:0159:00a1:0049:0031:00a9:0061:0089:0041:0049:0021:0119:0071:0025:0031:0039:01e1:0043:00a1:0059:0031:0073:0021:0049:0061:0059:0041:0043:01e1:0089:0061:00b9:0021:00d9:0021:0453:0141:00b3:0091:0119:0031:00c9:0041:03b9:0021:0093:0031:00f9:0021:0039:0021

View file

@ -9,7 +9,7 @@
#define DATA_FILE_NAME "data"
#define LCR_LOADING_COMMAND SDL_PumpEvents();
// #define LCR_FPS_GET_MS SDL_GetTicks() // uncomment for FPS measuring
#define LCR_FPS_GET_MS SDL_GetTicks() // uncomment for FPS measuring
#include "game.h"

6
game.h
View file

@ -1076,10 +1076,8 @@ void LCR_gameInit(int argc, const char **argv)
case 'R': quickLoad = 2; break;
case 'P': quickLoad = 3; break;
// TODO
default:
LCR_LOG2("unknown argument");
LCR_LOG1("unknown argument");
break;
}
}
@ -1199,7 +1197,7 @@ void LCR_gameTimeToStr(uint32_t timeMS, char *str)
void LCR_gameDrawPopupMessage(void)
{
#define _TEXT_SIZE 5
#define _OFFSET_V (LCR_EFFECTIVE_RESOLUTION_Y / 6)
#define _OFFSET_V (LCR_EFFECTIVE_RESOLUTION_Y / 8)
int textH = LCR_rendererComputeTextHeight(_TEXT_SIZE);
int textW = LCR_rendererComputeTextWidth(LCR_game.popupStr,_TEXT_SIZE);

View file

@ -2,29 +2,30 @@ WORK IN PROGRESS
'-._-'"'-_.-'"'-._.-'"'-._.-'"'- LICAR MANUAL -'"'-._.-'"'-._.-'"'-._.-'"'-._.-'
libre racing video game by drummyfish
libre racing video game by drummyfish (drummyfish@disroot.org)
released under CC0 1.0, public domain
~~~~~ GENERAL ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Licar is a relatively simple 3D stunt racing game inspired by other popular
games of the genre, such as Trackmania and Stunts. Unlike mainstream video
games, Licar is completely libre, i.e. free as in freedom (meaning its source
code and art assets are available for any use whatsoever), gratis (free of cost)
and its focus lies in being well programmed by adhering to minimalism and
rejecting harmful "modern" programming practices. The game aims to seflessly
bring joy and entertainment to all the people that might enjoy it, even those
who aren't able or willing to pay and/or watch ads, those owning very old and
weak computers and so on. It was made in whole by a single man as a completely
non-commercial product, the development was driven purely by love of the craft
and other living beings. Licar is also more than a game, for example it may
serve educational purposes or become a basis for new projects.
Licar is a relatively simple 3D stunt racing video game inspired by other
popular games of the genre, such as Trackmania and Stunts. Unlike mainstream
video games (even idie ones), Licar is completely libre, i.e. free as in freedom
(meaning its source code and art assets are available for any use whatsoever),
gratis (free of cost) and its focus lies in being well programmed by adhering to
minimalism and rejecting harmful "modern" programming practices. The game aims
to seflessly bring joy and entertainment to all the people that might enjoy it,
even those who aren't able or willing to pay and/or watch ads, those owning very
old and weak computers and so on. It was made in whole by a single man as a
completely non-commercial program, the development was driven purely by love of
the craft and other living beings to whom it might serve. Licar is also more
than a game, for example it may serve educational purposes or become a basis for
new projects.
The game runs on many platforms and comes in different versions depending on
what the platforms allow. Some versions may have more features or visual
"richness" than others. If anything mentioned in this manual is missing in your
game, it's probably because of limitations of your platform. On PCs and laptops
however everything should be supported.
game, it's probably because of limitations of your platform. On PCs and laptops,
however, everything should be supported.
~~~~~ RUNNING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -304,13 +305,18 @@ frustration but unless you want to fix this yourself, you'll have to just accept
it, the game is meant to be a simple entertainment. In other words this is a
feature :)
Q: I found a bug or have some other important comment.
A: Send me an email (found on top of this file).
Q: I have some other question (such as "Why is this not written in a modern
language?" or "What inspired you to make the game?" etc.)
A: Many questions I often get asked about my life philosophy and opinions can't
now be answered shortly. I have a website at http://www.tastyfish.cz. If you
don't find the answer there, my email is currently drummyfish@disroot.org, feel
free to ask me anything, but please remember I am not very social and don't
enjoy too much engaging in smalltalk or lengthy discussions about worldviews
etcetc.
A: Many questions I often get asked about my life and/or programming philosophy
can't now be answered in a tl;dr way without doing them injustice. I have a
website at http://www.tastyfish.cz, where I attempt to explain things, but
please note you will probably not like it. If you still decide to follow the
link and don't find your answer, feel to ask me anything over email, but please
remember I am not very social and don't enjoy too much engaging in smalltalk or
lengthy discussions about worldviews etcetc. I am certainly not interested in
having my opinions changed or changing someone else's mind by force.

View file

@ -6,10 +6,15 @@
Licar: racing module
This implements the racing physics and logic as well as replays and other
related things. It's possible to use this module alone if one wants to
related stuff. It's possible to use this module alone if one wants to
implement a program that doesn't need graphics, I/O etc.
Some comments:
- This module uses tinyphysicsengine, a small and simple physics engine that
uses "balls and springs" to model bodies (here the car) and kind of
"signed distance functions" to model environment (the map). The car is
strictly a soft body, but it's very "stiff" so that it behaves almost like
a rigid body.
- Replays are internally stored as follows: the replay consists of 16 bit
words representing changes in input at specific frame. In lowest 4 bits the
new input state is recorded, the remaining 12 bits record physics frame
@ -91,8 +96,6 @@ typedef int32_t LCR_GameUnit; ///< abstract game unit
#define LCR_CAR_GRASS_FACTOR 5
#define LCR_CAR_DIRT_FACTOR 3
#define LCR_CAR_ICE_FACTOR 1
//#define LCR_CAR_DRIFT_FACTOR 2 ///< only affects steering friction
#define LCR_CAR_DRIFT_FACTOR 2 ///< only affects steering friction
#define LCR_CAR_DRIFT_THRESHOLD_1 (LCR_GAME_UNIT / 10)
@ -218,7 +221,6 @@ void LCR_replayOutputStr(void (*printChar)(char))
#define PUTD(order) \
printChar('0' + (LCR_racing.replay.achievedTime / order) % 10);
PUTD(1000000) PUTD(100000) PUTD(10000)
PUTD(1000) PUTD(100) PUTD(10) PUTD(1)
#undef PUTD
@ -252,7 +254,7 @@ int LCR_replayLoadFromStr(char (*nextChar)(void),
LCR_racing.replay.eventCount = 0;
LCR_racing.replay.achievedTime = 0;
// has to be like this to force correct evaluation order:
// Has to be like this to force correct evaluation order:
c = nextChar() == LCR_RACING_VERSION1;
c |= (nextChar() == LCR_RACING_VERSION2) << 1;
@ -431,8 +433,10 @@ int LCR_replayRecordEvent(uint32_t frame, uint8_t input)
}
/**
Helper function for _LCR_racingEnvironmentFunction, returns closest point on
a map block placed at coordinate origin.
Helper function for _LCR_racingEnvironmentFunction, for given arbitrary point
returns the closest point on map block (of given type) placed at coordinate
origin (and in default orientation). This function defines shapes of map
blocks.
*/
TPE_Vec3 _LCR_racingBlockEnvFunc(TPE_Vec3 point, const uint8_t *block)
{
@ -758,7 +762,7 @@ TPE_Vec3 _LCR_racingBlockEnvFunc(TPE_Vec3 point, const uint8_t *block)
/**
For tinyphysicsengine, function that defines the shape of the static physics
world, returns closest point to any given point in space.
world. For any given point in space returns the environment's closest point.
*/
TPE_Vec3 _LCR_racingEnvironmentFunction(TPE_Vec3 point, TPE_Unit maxDist)
{
@ -821,7 +825,7 @@ TPE_Vec3 _LCR_racingEnvironmentFunction(TPE_Vec3 point, TPE_Unit maxDist)
TPE_ENV_NEXT(_LCR_racingBlockEnvFunc(point, // check it
LCR_currentMap.blocks + blockNum * LCR_BLOCK_SIZE),point)
// narrow the search range:
// Narrow the search range:
if (i % 2 == 0 && blockNum > start)
start = blockNum;
@ -869,8 +873,7 @@ uint8_t _LCR_racingCollisionHandler(uint16_t b1, uint16_t j1, uint16_t b2,
LCR_racing.carBody.joints[j1].velocity[0],
LCR_racing.carBody.joints[j1].velocity[1],
LCR_racing.carBody.joints[j1].velocity[2]),
TPE_vec3Minus(p,
LCR_racing.carBody.joints[j1].position)));
TPE_vec3Minus(p,LCR_racing.carBody.joints[j1].position)));
LCR_racing.crashState |= ((speed >= LCR_CAR_CRASH_SPEED_BIG) << 1) |
(speed >= LCR_CAR_CRASH_SPEED_SMALL);
@ -1527,10 +1530,10 @@ uint32_t LCR_racingStep(unsigned int input)
{
LCR_LOG2("roof squeezed, applying anti force")
TPE_Vec3 tmpVec = TPE_vec3Times(carUp,LCR_PHYSICS_UNIT / 16); // TODO: 16 magic con.
TPE_Vec3 tmpVec =
TPE_vec3Times(carUp,LCR_PHYSICS_UNIT / 16); // 16: magic const.
angle = TPE_F - 4 * angle; // 4 comes from above TPE_F / 4
tmpVec = TPE_vec3Times(tmpVec,angle);
if (angle <= 0)
@ -1540,7 +1543,7 @@ uint32_t LCR_racingStep(unsigned int input)
angle = 0;
}
// accelerate roof and wheels away from each other
// Accelerate roof and wheels away from each other:
for (int i = 0; i < LCR_CAR_JOINTS; ++i)
{
LCR_racing.carBody.joints[i].velocity[0] += (i == 4 ? 1 : -1) * tmpVec.x;

View file

@ -10,6 +10,9 @@
Some comments:
- The module uses small3dlib, a tiny software rasterization library. This
module knows nothing about I/O (windows, canvases, ...), it just say where
to draw pixels and what colors they should have.
- The map 3D model is divided into 4x4x4 chunks, i.e. 64 in total, out of
which only 8 are loaded at any time, depending on where the camera is and
where it is looking. This is to save resources, we don't draw the far away
@ -17,6 +20,10 @@
- Extremely simple LOD of far away chunks is implemented: we keep an 8x8x8
bit array of where there is empty space and where there is "something", then
for far away areas with "something" we just draw some 2D rectangles.
- RENDERING IS THE BOTTLENECK OF PERFORMANCE, it takes even much more time
than physics simulation, i.e. care should be taken to make code here very
optimized, namely the _LCR_pixelFunc3D function, as it is called for every
rasterized pixel.
*/
#define S3L_RESOLUTION_X LCR_EFFECTIVE_RESOLUTION_X
@ -108,12 +115,12 @@ struct
// pixel function precomputed values:
uint32_t previousTriID;
int triUVs[6];
int texSubsampleCount;
unsigned int flatAndTransparent; /**< If non-zero, transparent (dithered)
uint_fast16_t texSubsampleCount;
uint_fast16_t flatAndTransparent; /**< If non-zero, transparent (dithered)
polygons will be drawn without texture,
with color stored in this variable. */
#if LCR_SETTING_PARTICLES
uint16_t particleColor; /**< 0x0000 means no particles active. */
uint_fast16_t particleColor; /**< 0x0000 means no particles active. */
#endif
} LCR_renderer;
@ -338,7 +345,7 @@ void _LCR_pixelFunc3D(S3L_PixelInfo *pixel)
LCR_gameDrawPixelXYUnsafe(pixel->x,pixel->y,LCR_renderer.flatAndTransparent);
#else // LCR_SETTING_POTATO_GRAPHICS
// once we get a new triangle, we precompute things for it:
// New triangle? Precompute stuff for it:
if (pixel->triangleID != LCR_renderer.previousTriID)
{
LCR_renderer.previousTriID = pixel->triangleID;
@ -483,10 +490,14 @@ void _LCR_pixelFunc3D(S3L_PixelInfo *pixel)
}
}
/* Bottleneck: code from here below will be ran for every rasterized pixel,
optimizing it may significantly improve rendering performance. */
if (LCR_renderer.flatAndTransparent)
{
if (pixel->x % 2 == pixel->y % 2)
LCR_gameDrawPixelXYUnsafe(pixel->x,pixel->y,LCR_renderer.flatAndTransparent);
if ((pixel->x % 2) == (pixel->y % 2))
LCR_gameDrawPixelXYUnsafe(
pixel->x,pixel->y,LCR_renderer.flatAndTransparent);
else
S3L_zBufferWrite(pixel->x,pixel->y,S3L_MAX_DEPTH);
/* ^ Clear z-buffer if we didn't draw the pixel. Without this further
@ -1034,8 +1045,7 @@ uint8_t _LCR_buildMapModel(void)
LCR_IMAGE_WALL_CONCRETE : LCR_IMAGE_WALL_WOOD;
}
else
{ // TODO: tidy this mess?
{
if (LCR_mapBlockIsAccelerator(blockType))
triData |= LCR_IMAGE_GROUND_ACCEL;
else if (LCR_mapBlockIsFan(blockType))