Improve and optimize parallax generation and drawing

This commit is contained in:
Lucas S. Vieira 2024-10-27 21:06:46 -03:00
parent 09e17d3bbb
commit 774034dce6
5 changed files with 81 additions and 118 deletions

View file

@ -41,16 +41,14 @@ single = false
# =============
[island2]
width = 256
height = 40
u0 = 0
v0 = 44
scrollx = 0.25
y0 = 80
single = false
[[island2.parts]]
u0 = 0
v0 = 44
width = 256
# =============
# Water starts at BG1.
# Anything with v0 >= 256 is at BG1

View file

@ -20,32 +20,18 @@
height: u16
*/
// Holds a single parallax strip part.
// Parts are a horizontal slice of a single strip.
// Holds a single parallax strip for a level.
// A strip is a horizontally-repeating quad.
typedef struct {
uint8_t u0;
uint8_t v0;
uint8_t bgindex;
uint16_t width;
uint16_t height;
// Calculated on load
uint16_t offsetx;
} ParallaxPart;
// Holds a single parallax strip for a level.
// A strip may be a horizontally-repeating quad, or it
// may be composed of more than one quad that forms a bigger strip.
typedef struct {
uint8_t num_parts;
uint8_t bgindex;
uint8_t is_single;
int32_t scrollx;
int32_t speedx;
int16_t y0;
ParallaxPart *parts;
// Calculated on load
uint16_t width;
// State
int32_t rposx;

View file

@ -1,4 +1,5 @@
#include <stdio.h>
#include <assert.h>
#include "parallax.h"
#include "util.h"
@ -9,6 +10,11 @@
extern ArenaAllocator _level_arena;
extern uint8_t level_fade;
// Pre-allocated parallax polygons
#define PRL_POLYS 200
POLY_FT4 prl_polys[2][PRL_POLYS];
uint8_t prl_current_buffer = 0;
void
load_parallax(Parallax *parallax, const char *filename)
{
@ -33,40 +39,38 @@ load_parallax(Parallax *parallax, const char *filename)
sizeof(ParallaxStrip) * parallax->num_strips);
for(uint8_t i = 0; i < parallax->num_strips; i++) {
ParallaxStrip *strip = &parallax->strips[i];
strip->width = 0;
strip->num_parts = get_byte(bytes, &b);
strip->u0 = get_byte(bytes, &b);
strip->v0 = get_byte(bytes, &b);
strip->width = get_short_be(bytes, &b);
strip->height = get_short_be(bytes, &b);
strip->bgindex = get_byte(bytes, &b);
strip->is_single = get_byte(bytes, &b);
strip->scrollx = get_long_be(bytes, &b);
strip->speedx = get_long_be(bytes, &b);
strip->y0 = get_short_be(bytes, &b);
strip->rposx = 0;
strip->parts = alloc_arena_malloc(
&_level_arena,
sizeof(ParallaxPart) * strip->num_parts);
for(uint8_t j = 0; j < strip->num_parts; j++) {
ParallaxPart *part = &strip->parts[j];
part->u0 = get_byte(bytes, &b);
part->v0 = get_byte(bytes, &b);
part->bgindex = get_byte(bytes, &b);
part->width = get_short_be(bytes, &b);
part->height = get_short_be(bytes, &b);
part->offsetx = (j == 0)
? 0
: (strip->parts[j-1].offsetx + strip->parts[j-1].width);
strip->width += part->width;
}
}
free(bytes);
// Pre-allocate polygons
prl_current_buffer = 0;
for(uint32_t i = 0; i < PRL_POLYS; i++) {
for(int j = 0; j < 2; j++) {
POLY_FT4 *poly = &prl_polys[j][i];
setPolyFT4(poly);
setRGB0(poly, 0, 0, 0);
}
}
}
void
parallax_draw(Parallax *prl, Camera *camera,
uint8_t tx_mode, int32_t px, int32_t py, int32_t cx, int32_t cy)
{
uint32_t poly_count = 0;
// Camera left boundary (fixed 20.12 format)
int32_t camera_vx = (camera->pos.vx - (CENTERX << 12));
@ -76,46 +80,52 @@ parallax_draw(Parallax *prl, Camera *camera,
ParallaxStrip *strip = &prl->strips[si];
// Cast multiplication to avoid sign extension on bit shift
// This gets the mult. result but also removes the decimal part
int32_t stripx = (uint32_t)(camera_vx * strip->scrollx) >> 24;
int32_t stripx = (uint32_t)(camera_vx * -strip->scrollx) >> 24;
// Coordinates currently start drawing at screen center, so
// push them back one screem
stripx -= SCREEN_XRES;
// Update strip relative position when there's speed involved
strip->rposx -= strip->speedx;
if((strip->rposx >> 12) < -((int32_t)strip->width))
strip->rposx = 0;
for(uint8_t pi = 0; pi < strip->num_parts; pi++) {
ParallaxPart *part = &strip->parts[pi];
// Calculate part X position based on factor and camera (int format)
int32_t vx = ((int32_t)part->offsetx) - stripx + (strip->rposx >> 12);
int32_t vx = stripx + (strip->rposx >> 12);
// Given that each part is a horizontal piece of a strip, we assume
// that these parts repeat at every (strip width), so just draw
// all equal parts now at once, until we exhaust the screen width
for(int32_t wx = vx;
wx < (int32_t)(SCREEN_XRES + part->width);
wx += part->width)
wx < (int32_t)(SCREEN_XRES + strip->width);
wx += strip->width)
{
// Don't draw if strip is outside screen
if((wx + part->width) < 0) continue;
if((wx + strip->width) < 0) continue;
// TODO: 6 or 8 Depends on CLUT!!!
uint16_t curr_px = (uint16_t)(px + ((uint32_t)part->bgindex << 6));
uint16_t curr_cy = (uint16_t)(cy + part->bgindex);
uint16_t curr_px = (uint16_t)(px + ((uint32_t)strip->bgindex << 6));
uint16_t curr_cy = (uint16_t)(cy + strip->bgindex);
POLY_FT4 *poly = (POLY_FT4 *)get_next_prim();
increment_prim(sizeof(POLY_FT4));
setPolyFT4(poly);
//POLY_FT4 *poly = (POLY_FT4 *)get_next_prim();
//increment_prim(sizeof(POLY_FT4));
//setPolyFT4(poly);
POLY_FT4 *poly = &prl_polys[prl_current_buffer][poly_count];
if(poly->r0 != level_fade)
setRGB0(poly, level_fade, level_fade, level_fade);
poly->tpage = getTPage(tx_mode & 0x3, 0, curr_px, py);
poly->clut = getClut(cx, curr_cy);
setXYWH(poly, wx, strip->y0, part->width, part->height);
setUVWH(poly, part->u0, part->v0, part->width - 1, part->height - 1);
setXYWH(poly, wx, strip->y0, strip->width, strip->height);
setUVWH(poly, strip->u0, strip->v0, strip->width - 1, strip->height - 1);
sort_prim(poly, OT_LENGTH - 2); // Layer 5: Background
poly_count++;
assert(poly_count < PRL_POLYS);
// If drawing a single time, stop now
if(strip->is_single) break;
}
}
}
prl_current_buffer ^= 1;
}

View file

@ -21,37 +21,27 @@ def tofixed12(value: float) -> int:
@dataclass
class ParallaxPart:
class ParallaxStrip:
u0: int = 0
v0: int = 0
bg_index: int = 0
width: int = 0
height: int = 0
def write_to(self, f):
f.write(c_ubyte(self.u0))
f.write(c_ubyte(self.v0))
f.write(c_ubyte(self.bg_index))
f.write(c_ushort(self.width))
f.write(c_ushort(self.height))
@dataclass
class ParallaxStrip:
bg_index: int = 0
single: bool = False
scrollx: float = 0
speedx: float = 0
y0: int = 0
parts: [ParallaxPart] = field(default_factory=list)
def write_to(self, f):
f.write(c_ubyte(len(self.parts)))
f.write(c_ubyte(self.u0))
f.write(c_ubyte(self.v0))
f.write(c_ushort(self.width))
f.write(c_ushort(self.height))
f.write(c_ubyte(self.bg_index))
f.write(c_ubyte(int(self.single)))
f.write(c_int(tofixed12(self.scrollx)))
f.write(c_int(tofixed12(self.speedx)))
f.write(c_short(self.y0))
for p in self.parts:
p.write_to(f)
@dataclass
@ -73,29 +63,13 @@ def parse(data) -> Parallax:
strip.scrollx = strip_data.get("scrollx", 0)
strip.speedx = strip_data.get("speedx", 0)
strip.y0 = strip_data.get("y0")
height = strip_data.get("height") # Reserved
parts = strip_data.get("parts")
strip.width = strip_data.get("width")
strip.height = strip_data.get("height")
if not parts:
# This is a single-piece strip
part = ParallaxPart()
v0 = strip_data.get("v0")
part.bg_index = int(v0 / 256)
part.u0 = strip_data.get("u0")
part.v0 = v0 % 256
part.width = strip_data.get("width")
part.height = height
strip.parts.append(part)
else:
for part_data in parts:
part = ParallaxPart()
v0 = part_data.get("v0")
part.bg_index = int(v0 / 256)
part.u0 = part_data.get("u0")
part.v0 = v0 % 256
part.width = part_data.get("width")
part.height = height
strip.parts.append(part)
strip.bg_index = int(v0 / 256)
strip.u0 = strip_data.get("u0")
strip.v0 = v0 % 256
p.strips.append(strip)
return p

View file

@ -1,20 +1,15 @@
// -*- mode: c; -*-
struct ParallaxPart {
struct ParallaxStrip {
u8 u0;
u8 v0;
u8 tex_idx;
be u16 width;
be u16 height;
};
struct ParallaxStrip {
u8 num_parts;
u8 tex_idx;
u8 is_single;
be s32 scrollx;
be s32 speedx;
be s16 y0;
ParallaxPart parts[num_parts];
};
struct Parallax {