2DArray,
@2DArray@mastodon.gamedev.place avatar
2DArray,
@2DArray@mastodon.gamedev.place avatar
2DArray,
@2DArray@mastodon.gamedev.place avatar
cominu,
@cominu@mastodon.gamedev.place avatar

@2DArray looking great! Any tips about how to implement that dithering in a performant way?

2DArray,
@2DArray@mastodon.gamedev.place avatar

@cominu dunno your level of experience here, so basics first:

you usually want to store a dither pattern in a premade array of values instead of live-computing them. the wikipedia page for "ordered dithering" has some examples of of these arrays for bayer dithering, like in the clip (they show them as matrices, you can flatten them). blue noise can be done the same, just uses a differently-shuffled array

and then it's really just checking each pixel against a value in the tiled dither pattern

2DArray,
@2DArray@mastodon.gamedev.place avatar

@cominu my main performance insight in this renderer is applying some general cache-awareness to the wolf3D-raycaster situation:

these raycasters use vertical lines as their drawing primitives (one ray per vertical line on the screen). this is very rough for the cache: playdate's framebuffer is stored in "english reading order" (a row at a time, top to bottom), so stepping downward 1 pixel means jumping 52 bytes in the buffer...and a pixel is 1 bit, so it has to do bit-twiddling for each pixel

2DArray,
@2DArray@mastodon.gamedev.place avatar

@cominu basically, drawing a bunch of 1-pixel-wide vertical lines is maybe the slowest way you could draw this

instead, when raycasting, i store the line-heights in a 400-item array (one for each column of the screen's resolution), and then afterward, i step over the framebuffer in reading order, writing 8-bit strips of pixels. so it writes the frame buffer 1 full byte at a time, and steps over the line-heights array 1 value at a time (with jumps at the end of a row). much better for the cache!

2DArray,
@2DArray@mastodon.gamedev.place avatar

@cominu and with that in place, the actual dithering part is really just a comparison

(orderedDither is my dither-pattern as an array of bytes, ditherIndex is the tiled index into that array, and ditherValue is the intended brightness of the pixel. this is C, so dither will either become 0 or 1 depending on whether or not it passed the threshold, and that gets multiplied in with the pixel's baseline "is this part of a wall that needs to be filled in" value)

cominu,
@cominu@mastodon.gamedev.place avatar

@2DArray thanks, super clear explanation! I always thought there was some black magic behind the dithering algo and...in the end it's really, really simple 😅

2DArray,
@2DArray@mastodon.gamedev.place avatar

@cominu yeah dithering is startlingly simple once you've got a handle on what it means! the really general form is "when you're gonna reduce the bit-depth of a value, add some random noise to it first"

2DArray,
@2DArray@mastodon.gamedev.place avatar
2DArray,
@2DArray@mastodon.gamedev.place avatar

starting on player-controls

the camera is following behind a car...but you can't really tell because the car isn't rendered yet

more of the bendy-corridor traversal, but now the camera motion is much smoother than before

fsouchu,
@fsouchu@mastodon.gamedev.place avatar

@2DArray unrelated question : how did you get a video not destroyed by Mastodon compression ??

2DArray,
@2DArray@mastodon.gamedev.place avatar

@fsouchu I'm honestly not sure lol. the latest clips are recorded with OBS at 720p, so I could capture above 30fps (which means I had to scale up the source when recording - maybe that's what helped?)

2DArray,
@2DArray@mastodon.gamedev.place avatar
2DArray,
@2DArray@mastodon.gamedev.place avatar

a floor

this drops my fps to 40 (max possible on playdate is 50) but i'm hoping/assuming i can find more stuff to optimize. worst case, locking the game to 30fps seems like it would be OKAY, and maybe even the more sane choice for a handheld (to save on battery power)

the real ideal thing would be if you could pick between 30fps (power-saver mode) and 50fps (try-hard mode)

video/mp4

2DArray,
@2DArray@mastodon.gamedev.place avatar

better floor (now with ambient occlusion!)

back up to "usually 50fps" on the device, sometimes dips down to around 47

more footage of the corridor-racing game, now with a nicer checkered floor which gets darker near the walls

Leukbaars,
@Leukbaars@mastodon.gamedev.place avatar

@2DArray very cool, makes me wanna pick up my little playdate 3d engine project again :)

fsouchu,
@fsouchu@mastodon.gamedev.place avatar

@2DArray perfect trajectory type of game? trackmania-like?

2DArray,
@2DArray@mastodon.gamedev.place avatar

@fsouchu that's the ticket! I really want to capture some of the drifting feel from TM2 (canyon) but I haven't gotten to that part yet since I've mostly been focusing on gfx stuff

2DArray,
@2DArray@mastodon.gamedev.place avatar

working on a drifting mechanic

it needs more tuning but i'm absolutely stoked about it already

more of the car-corridor playdate game, but now the car is doing high-speed drifts around the sharper corners

2DArray,
@2DArray@mastodon.gamedev.place avatar

starting to draw a proper car - i did this first version in a really basic way so i thought it'd tank my performance, but it turns out it still stays at like 45+ fps on the device. not bad!

i'm very bad at lowpoly modeling so the car accidentally looks like an old-timey beater, but i think that's sorta funny so i'll probably try to keep that vibe

video/mp4

2DArray,
@2DArray@mastodon.gamedev.place avatar

night sky - drops me to about 40fps on the device, but i think i can still find more room to optimize

would be nice to add some clouds or something...

video/mp4

2DArray,
@2DArray@mastodon.gamedev.place avatar

adding some presentation at the start of the race, and some basic timer/scoring stuff

all hail lobster font, lobster font will lead us to salvation

more footage of the racing game - now it includes a "3, 2, 1, start" countdown at the beginning, and when you reach the end, it reports your completion time

2DArray,
@2DArray@mastodon.gamedev.place avatar

made a fancier finish-line and now i'm gonna need to write my own triangle-rasterizer instead of relying on the playdate sdk's fillTriangle() helper - i'd like to get rasterized meshes to use the same dithering as the walls/floors/sky, and also i need the meshes to get occluded by walls

the racing game's car slowly approaches a track's finish line, which now has a black and white checkered banner hanging above it

2DArray,
@2DArray@mastodon.gamedev.place avatar
fsouchu,
@fsouchu@mastodon.gamedev.place avatar

@2DArray did you get anywhere with SIMD rasterization?

2DArray,
@2DArray@mastodon.gamedev.place avatar

@fsouchu nope, lol. i still haven't tried the thing i was talking about at first, but i goofed around a bit with some related-but-less-drastic simd usage, and failed to get any actual speedup (which is what has always happened whenever i've tried to use simd in general)

2DArray,
@2DArray@mastodon.gamedev.place avatar

@fsouchu i did get a nice speedup on the rasterizer by doing that "splat a slice of the dither pattern into an 8-or-32 bit scanline-strip all at once, instead of iterating individual pixels" thing (i already had start/end sub-indices inside each strip, so i did some bitwise shenanigans to create a mask with leading and trailing zeroes, then used that to splat the pattern)

unfortunately, i need per-pixel iteration for wall occlusion, so i can't actually use that 🙃

fsouchu,
@fsouchu@mastodon.gamedev.place avatar

@2DArray 32 bits at once is what I do also - see the 3d sample in the SDK for a solid "fill line" starting point

twitonatrain,

@2DArray @fsouchu
I might well or be following this I enough detail but could you read the current 32 bits then mask in your fill pattern and write back? Or does multiple walls make that impractical

2DArray,
@2DArray@mastodon.gamedev.place avatar

@twitonatrain the dither pattern is storing u8 thresholds (to compare against a pixel's intended brightness), so it's not currently a blittable sprite! I could convert it into several ready-to-go sprites for different grey levels, but I imagine it'd lose some smoothness (especially in the sky, where it's a lot of smooth fades)...but maybe that'll become necessary at some point

twitonatrain,

@2DArray Ah, see I told you I wasn't paying enough attention. It's all looking lovely though.

2DArray,
@2DArray@mastodon.gamedev.place avatar

@twitonatrain thanks! and honestly your idea is a good one which i've been trying to find ways to incorporate...i might have an avenue to use it in the mesh rasterizer (which does use blittable patterns!), if it can detect that a certain 32-bit slice of a scanline has no chance of being occluded by a wall (which means the per-pixel checks aren't needed there)

2DArray,
@2DArray@mastodon.gamedev.place avatar

working on replay recording/playback - yes, the clip looks the same as the others in the thread, but it's a pretty good run since i was retrying the map. if you think "i see places where the racing line could have been improved" then the game is working, lol

replay data is quite small: stores keyframes containing a bitmask of user-input states (one byte), along with how many frames you held that state for (one byte). a "normal" 30 second replay tends to be around 200 bytes

2DArray,
@2DArray@mastodon.gamedev.place avatar

unfortunately i doubt there'll be a way to do any online-sharing because of playdate limitations (i'm gonna try to get libcurl to work anyway though - i think they use it for OS features, they just don't expose any networking stuff in their sdk at this point)

isziaui,
@isziaui@mstdn.social avatar

@2DArray Maybe you can find a way to cram it into a QR code.

2DArray,
@2DArray@mastodon.gamedev.place avatar

@isziaui does seem possible to export like that...but then you'd still have to get the data back into someone else's device!

best I've thought of so far is a companion app that you run on a PC, and they talk to each other over USB. not ideal, but it'd save you from needing to boot the playdate into data-drive mode, at least. an over-engineered version could talk to a webserver from there, lol

2DArray,
@2DArray@mastodon.gamedev.place avatar
2DArray,
@2DArray@mastodon.gamedev.place avatar

smarter sampling on the floor...

when it samples the floor tex, it raycasts the texelspace grid to see how many screen-pixels it'll step along the scanline before hitting the next texel, and it can reuse the latest sample until then - fewer samples, sharper result!

it also does some smooth LOD, where it starts using a bigger and bigger "screen-pixel steps per sample" ratio as pixels get farther away

(before/after)

A similar screenshot of the racing game, but now the checkerboard pattern has much sharper edges between its squares.

2DArray,
@2DArray@mastodon.gamedev.place avatar

added a realtime shadow for the player's car

it only works on the floor (not the walls) but that's probably okay-enough for me

More footage of the racing game, now with a shadow from the car cast onto the floor.

ruby0x1,
@ruby0x1@mastodon.gamedev.place avatar

@2DArray looks good

2DArray,
@2DArray@mastodon.gamedev.place avatar

@ruby0x1 thank you!

allpurposemat,
@allpurposemat@mastodon.gamedev.place avatar

@2DArray this looks so good!

fsouchu,
@fsouchu@mastodon.gamedev.place avatar

@2DArray this is not blue noised dithered, I am shocked 😱

2DArray,
@2DArray@mastodon.gamedev.place avatar

@fsouchu yeeep the mesh rasterizer is using simpler patterns everywhere so it can do the 32-bit-blitting stuff (when it knows there's no risk of wall-occlusion)

that said, I could probably bake a few blittable blue noise patterns for this. I'm not quite happy with the look of the shadow yet, so maybe that'd do the trick!

fsouchu,
@fsouchu@mastodon.gamedev.place avatar

@2DArray I guess shadow on wall is doable no? are you drawing walls pixel row by pixel row?

2DArray,
@2DArray@mastodon.gamedev.place avatar

@fsouchu walls are "wolfenstyle" so they start by doing a per-column raycast, and save the resulting screenspace wall-heights to an array. then, during the fullscreen per-pixel pass (walls/floor/sky), it just has to check if a pixel is above/below the wall-range for its screen-x position

the shadow is a copy of the car mesh with a weird/flattened local-to-world matrix - what are you imagining for wall-shadows? all i've thought of is "pretend the nearby wall is a plane, project a mesh onto that"

fsouchu,
@fsouchu@mastodon.gamedev.place avatar

@2DArray render car shadow to a buffer (projected on floor) and sample rows if wall lands on a shadow pixel?

fsouchu,
@fsouchu@mastodon.gamedev.place avatar

@2DArray I mean - copy the row portion that’s left or right from wall pixel as a vertical slice

2DArray,
@2DArray@mastodon.gamedev.place avatar

@fsouchu oh that's wackier than i was expecting! will give this some thought

ikesau,

@2DArray looks like it's just me but i think i preferred it without the shadow 🙂

2DArray,
@2DArray@mastodon.gamedev.place avatar

@ikesau to me, the shadow is looking a little too strong at the moment, at "50% alpha" - so maybe it'll look better to you once it's a bit milder!

2DArray,
@2DArray@mastodon.gamedev.place avatar

kinda going off the deep end here but now the car can cast its shadow onto the walls

it's kind of a ridiculously subtle effect, but it ties the mesh-geo, the raycaster-geo, and the skybox together, so uhhh maybe that's something

the racing-game car slowly drives past some walls, and its shadow gets cast correctly onto both the floor and the walls.

fsouchu,
@fsouchu@mastodon.gamedev.place avatar

@2DArray excellent 👌

2DArray,
@2DArray@mastodon.gamedev.place avatar

@fsouchu thanks!

ended up doing the less-fancy "pretend the nearby wall is a flat plane, project the car-mesh onto that" idea...mostly because it seemed easier to implement

now in the worst case, i get down to like 33fps (player car, player floor-shadow, player wall-shadow, ghost car, giant "START" mesh) but hey i'm still above 30 lol and i might be out of ideas for more wacky gfx shit to add, so i might be getting away with it

shanecelis,
@shanecelis@mastodon.gamedev.place avatar

@2DArray That’s some smooth, stable dither. Wow.

2DArray,
@2DArray@mastodon.gamedev.place avatar

starting to think about a title screen but i'm not really sure wut to do yet

but hey i have an easy way to make text into 3D meshes (thank you, blender!) so that seems like a sane place to start

A black Playdate screen with the text "Trackminia" displayed in the middle. The text slowly rotates slightly back and forth in 3D, sort of like a wind vane.

fsouchu,
@fsouchu@mastodon.gamedev.place avatar

@2DArray suntrack? the title screen totally misses the sun presence imho

2DArray,
@2DArray@mastodon.gamedev.place avatar

@fsouchu i was thinking about drawing the sun...but i think at the moment i'm interested in getting the title card to look like it's own thing, instead of just showing what the normal gameplay looks like (dunno if it's the right call, but that's where i'm at so far)

the motion here is temp, but it's partway to something more interesting...

a title card reading "Trackminia" in front of a waving checkered flag

2DArray,
@2DArray@mastodon.gamedev.place avatar
2DArray,
@2DArray@mastodon.gamedev.place avatar

been pretty busy for a few days, so not much progress here - but i've started getting into a track editor

initially this is just for me to make some built-in tracks, but i might polish it to make it user-facing if i can figure out a comfy way for people to share their maps

some footage of the racing game - the video starts on the title screen, then the user goes to a main menu and picks "build: new track." they use a 2D interface to set up a spiraling track, and then they do a test-race through the route that they've made.

2DArray,
@2DArray@mastodon.gamedev.place avatar

i can now save tracks as files, and then load them - the ghost-car in this clip is included in the track data. the spiral track file (including that ghost's replay) is 161 bytes

zipping the file actually makes it larger (241 bytes) so i think i did good. lol

more footage of the racing game. the video starts in a "load track" menu, which shows 2D previews of the track files which are available to play. a spiral-shaped track is chosen, and then the player races through it while competing against a pre-recorded ghost car

2DArray,
@2DArray@mastodon.gamedev.place avatar

conveniently, i already wrote a playdate serializer for C, for that previous music-maker app

here's the code for serializing/deserializing a track and its replay. serialization and deserialization use these same functions (the Serializer has a flag to tell it whether it's reading or writing) - so there's no need for annoying/error-prone "near duplicate" routines for input/output

it supports ascii format (more legible) and binary format (smaller file)

Another small bit of code, showing two functions called "Serialize Track Piece" and "Serialize Replay"
An example of what a track file looks like in ASCII mode - values have descriptive labels and related pieces of data are grouped together with curly braces and indentation
An example of a track file in binary format - it's a bunch of garbled looking nonsense, but there's much less of it compared to the ASCII version

2DArray,
@2DArray@mastodon.gamedev.place avatar

mostly boring backend stuff lately, but here's a clip of a track i made on the playdate and then loaded in the desktop simulator app - it includes four replays to compete against (bronze, silver, gold, author), and it seems like i can afford to show them all at once!

another video of the racing game. this time we load a track from a screen showing a top-view 2D preview of the map, and then the player races against four ghost-cars of varying skill levels.

fsouchu,
@fsouchu@mastodon.gamedev.place avatar

@2DArray would it be worth solidifying cars at distance ? to make them more obvious (thinking on device vs light conditions)

2DArray,
@2DArray@mastodon.gamedev.place avatar

@fsouchu yeah that seems like a good idea! may need to sort the cars by camera-distance but that seems cheap since there are so few of them anyway

2DArray,
@2DArray@mastodon.gamedev.place avatar

reduced the framerate to 30fps since it still beats that on the device, but in doing so i instantly radicalized myself into one of those "30fps is literally unplayable" people, so now i guess i have to optimize for 40 instead

(30 is fine with me in other games, i'm just used to 50 in this one)

2DArray,
@2DArray@mastodon.gamedev.place avatar

whelp turns out i can't figure out how to get it back to 40 (particularly with 4 ghosts active) but 30 actually looks fine on the device - the lower framerate is only noticeable to me when testing in the desktop simulator

something something magic playdate screen. good enough!

Felice,
@Felice@mastodon.social avatar

@2DArray That blue noise works incredibly well, and it's so stable. I wonder how I never heard of it in 20 years of rendering work. Looks just like error-diffusion dithering. Do you know if there's any good sample code I could look at? I'd love to play with it and get to know how to use it.

2DArray,
@2DArray@mastodon.gamedev.place avatar

@Felice thanks!

implementation is dead simple: it's the exact same as what you'd do for Bayer dithering (dither pattern is a pixel-grid of brightness values, each final screen pixel becomes either black or white by comparing its intended brightness to a value in the pattern), but with a blue-noise texture as the pattern

i got a texture from here:
https://momentsingraphics.de/BlueNoise.html

in this case, the dither pattern is fullscreen, but tiling a smaller pattern is fine, too

fsouchu,
@fsouchu@mastodon.gamedev.place avatar

@2DArray hu… that would assume perfect frame alignement between runs

2DArray,
@2DArray@mastodon.gamedev.place avatar

@fsouchu yep! floating point is deterministic and the app is single-threaded, so (at least as far as i've seen...) the only thing that's unpredictable is the user-input. i wouldn't be surprised if the simulator and playdate produce replays which are incompatible with each other due to compiler differences...but i haven't serialized any replays, so i haven't seen that happen yet lol. if it turns out to be a problem, i can resort to a heftier format!

2DArray,
@2DArray@mastodon.gamedev.place avatar

@fsouchu (and crucially, the app is using a fixed tick-rate, not a variable physics-step!)

fsouchu,
@fsouchu@mastodon.gamedev.place avatar

@2DArray I suspect you have no lua? cause I find my game to be highly unpredictable in term of perf (eg not a flat line for a static scene)

2DArray,
@2DArray@mastodon.gamedev.place avatar

@fsouchu correct, all C! but yeah even when the performance is variable, the game just moves slower instead of using a variable deltaTime (I'm now assuming I'm gonna lock to 30fps at some point because I can't stop adding visual stuff, so I just need to make sure it's always able to run faster than that - but even if it was a problem, the sim is WAY faster than the rendering, so it could do a multi-stepping fixed-tick for the physics whenever it was dropping frames)

fsouchu,
@fsouchu@mastodon.gamedev.place avatar

@2DArray racing game x lighthouse? 🤔

2DArray,
@2DArray@mastodon.gamedev.place avatar

@fsouchu racing game: yes! lighthouse: not quite!

this is just a debug view for now, hopefully will have some time today to try drawing the actual viewpoint. big hint: it's testing 400 rays

fsouchu,
@fsouchu@mastodon.gamedev.place avatar

@2DArray raycasting floor?

2DArray,
@2DArray@mastodon.gamedev.place avatar

@fsouchu if you mean "this current render is showing a top-down view of the floor in a raycasting setup," then yes!

fsouchu,
@fsouchu@mastodon.gamedev.place avatar

@2DArray parametric intersection ? to avoid the blocky look of mode7 textures?

2DArray,
@2DArray@mastodon.gamedev.place avatar

@fsouchu yeah exactly! currently the walls are all either axis-aligned lines or quadrant-aligned circular arcs, and both of those allow reasonably-fast ray intersection tests

(the arc test is "ray vs circle" followed by checking the sign of the circle's local hit-x and hit-y to see if it's inside the arc's chosen quadrant)

2DArray,
@2DArray@mastodon.gamedev.place avatar

@fsouchu that said...i might end up allowing arbitrarily-rotated lines and arbitrary arcs for more level design options - the arc test could replace that quadrant trick with:

dot(localHitPoint, arcBisectDirection)

and check that against some precomputed per-arc threshold, something like

cos(arcHalfAngle) * arcRadius

  • All
  • Subscribed
  • Moderated
  • Favorites
  • random
  • DreamBathrooms
  • magazineikmin
  • thenastyranch
  • modclub
  • everett
  • rosin
  • Youngstown
  • slotface
  • ethstaker
  • mdbf
  • kavyap
  • osvaldo12
  • InstantRegret
  • Durango
  • megavids
  • ngwrru68w68
  • tester
  • khanakhh
  • love
  • tacticalgear
  • cubers
  • GTA5RPClips
  • Leos
  • normalnudes
  • provamag3
  • cisconetworking
  • anitta
  • JUstTest
  • All magazines