Skip to content

Commit e2317f2

Browse files
committed
WIP: Dedup tiles
1 parent d16046b commit e2317f2

File tree

4 files changed

+53
-14
lines changed

4 files changed

+53
-14
lines changed

encoder/encode_sega_video.py

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
# that people want to expand on this goofy project and worry about
3535
# compatibility, we define a constant for the file format that is written into
3636
# the output file.
37-
FILE_FORMAT = 3
37+
FILE_FORMAT = 4
3838

3939
# Number of tiles (w, h) for fullscreen and thumbnail sizes.
4040
FULLSCREEN_TILES = (32, 28)
@@ -493,7 +493,7 @@ def dump_debug_file(frame_dir, audio_dir, args):
493493
run(args.debug, check=True, args=ffmpeg_args)
494494

495495

496-
def png_to_sega_frame(in_path, out_file, expected_tiles):
496+
def png_to_sega_frame(in_path, out_file, expected_tiles, dedup, pal=0):
497497
img = Image.open(in_path)
498498
pixels = img.getdata()
499499
width, height = img.size
@@ -513,6 +513,7 @@ def png_to_sega_frame(in_path, out_file, expected_tiles):
513513

514514
# Each tile is 8x8 pixels, 4 bit palette index per pixel.
515515
binary_tiles = b''
516+
tile_map = []
516517

517518
# The image dimensions should each be a multiple of 8 already.
518519
assert width % 8 == 0 and height % 8 == 0
@@ -522,6 +523,8 @@ def png_to_sega_frame(in_path, out_file, expected_tiles):
522523
# We should have a fullscreen image.
523524
assert (tiles_width, tiles_height) == expected_tiles
524525

526+
binary_tile_to_index = {}
527+
525528
for tile_y in range(tiles_height):
526529
for tile_x in range(tiles_width):
527530
tile = []
@@ -539,15 +542,33 @@ def png_to_sega_frame(in_path, out_file, expected_tiles):
539542

540543
tile.append(palette_index)
541544

542-
binary_tiles += pack_tile(tile)
545+
binary_tile = pack_tile(tile)
546+
index = binary_tile_to_index.get(binary_tile, None)
547+
if dedup == False:
548+
index = None
549+
550+
if index is None:
551+
index = len(binary_tile_to_index)
552+
binary_tile_to_index[binary_tile] = index
553+
binary_tiles += binary_tile
554+
555+
tile_map.append(index | (pal << 13))
543556

544-
# SegaVideoFrameHeader contains the palette only
557+
# Output the palette
545558
out_file.write(pack_palette(palette))
559+
560+
if dedup == True:
561+
# Pack the tile map
562+
out_file.write(pack_tile_map(tile_map))
563+
# The number of tiles to follow
564+
out_file.write(len(binary_tile_to_index).to_bytes(4, 'big'))
565+
546566
# Actual tile data follows
547567
out_file.write(binary_tiles)
548568

549-
# A palette is always 32 bytes
550-
return 32 + len(binary_tiles)
569+
# A palette is always 32 bytes, the tile map is 1792 bytes, the number of
570+
# tiles is encoded in 4 bytes.
571+
return 32 + 1792 + 4 + len(binary_tiles)
551572

552573

553574
def sega_color_map(value):
@@ -587,6 +608,13 @@ def pack_tile(palette_indexes):
587608
return packed
588609

589610

611+
def pack_tile_map(tile_indexes):
612+
packed = b''
613+
for idx in tile_indexes:
614+
packed += idx.to_bytes(2, 'big')
615+
return packed
616+
617+
590618
def pack_palette(palette):
591619
packed = b''
592620
assert(len(palette) <= 16)
@@ -654,7 +682,8 @@ def write_chunk(f, state):
654682
chunk_frame_data_len = 0
655683
for i in range(chunk_frame_count):
656684
input_path = state.frame_paths[state.frame_path_index]
657-
bytes_written = png_to_sega_frame(input_path, f, FULLSCREEN_TILES)
685+
bytes_written = png_to_sega_frame(input_path, f, FULLSCREEN_TILES,
686+
dedup=True, pal=(1 if (i % 2) else 0))
658687
chunk_frame_data_len += bytes_written
659688
state.frame_count -= 1
660689
state.frame_path_index += 1
@@ -854,7 +883,7 @@ def generate_thumbnail(args, fullcolor_dir, thumb_dir):
854883

855884
# Then convert to Sega format.
856885
with open(sega_frame_out, 'wb') as f:
857-
png_to_sega_frame(thumb_out, f, THUMBNAIL_TILES)
886+
png_to_sega_frame(thumb_out, f, THUMBNAIL_TILES, dedup=False)
858887

859888
print('Thumbnail generated from frame #{}.'.format(thumb_index + 1))
860889

encoder/generate_trivial_tilemap.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ def write_trivial_tilemap(path, pal_num, width, height):
1717
for i in range(width * height):
1818
# Stripped down version of TILE_ATTR_FULL() macro without priority or
1919
# flipping. Assuming no deduplication of tiles.
20+
# #define TILE_ATTR_HFLIP_SFT 11
21+
# #define TILE_ATTR_VFLIP_SFT 12
22+
# #define TILE_ATTR_PALETTE_SFT 13
23+
# #define TILE_ATTR_PRIORITY_SFT 15
24+
# #define TILE_ATTR_FULL(pal, prio, flipV, flipH, index) (((flipH) << TILE_ATTR_HFLIP_SFT) + ((flipV) << TILE_ATTR_VFLIP_SFT) + ((pal) << TILE_ATTR_PALETTE_SFT) + ((prio) << TILE_ATTR_PRIORITY_SFT) + (index))
2025
map_value = (pal_num << 13) | i
2126
f.write(map_value.to_bytes(2, 'big'))
2227

software/player/inc/segavideo_format.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
#endif
2121

2222
#define SEGAVIDEO_HEADER_MAGIC "what nintendon't"
23-
#define SEGAVIDEO_HEADER_FORMAT 0x0003
23+
#define SEGAVIDEO_HEADER_FORMAT 0x0004
2424

2525
// This header appears at the start of the file in both embedded and streaming
2626
// mode. Each one is exactly 8kB, so they can form the basis of a catalog
@@ -97,6 +97,12 @@ typedef struct SegaVideoFrame {
9797
// always considered fully transparent by the VDP, and all other entries are
9898
// considered fully opaque. The alpha bits are always ignored.
9999
uint16_t palette[16];
100+
// Custom tile map. Each entry is a tile number in the low 10 bits, and a
101+
// palette number in bits 13-14 (left blank by the encoder, set by the player
102+
// as it flips frames).
103+
uint16_t tileMap[32 * 28];
104+
// Unique tiles below. May not use all 32x28 tiles.
105+
uint32_t numTiles;
100106
// Each tile is a packed array of 64 (8x8) 4-bit palette indexes (16 words).
101107
// Uses trivial_tilemap_0 or trivial_tilemap_1.
102108
uint32_t tiles[8 * 32 * 28]; // 32 bytes per tile (8*uint32_t), 32x28 tiles

software/player/src/segavideo_player.c

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ static uint32_t nextFrameNum;
5959
// Hard-coded for now. Fullscreen video only.
6060
#define MAP_W 32
6161
#define MAP_H 28
62-
#define NUM_TILES (32 * 28) // 896
62+
#define MAX_NUM_TILES (32 * 28) // 896
6363
#define FRAME_TILE_INDEX 0 // Overwrites 16 system tiles, but we need space
6464

6565
// NOTE: We use the XGM2 driver. With the PCM-specific drivers, I found audio
@@ -371,13 +371,12 @@ static bool nextVideoFrame() {
371371

372372
// We alternate tile and palette indexes every frame.
373373
bool second = currentFrameNum & 1;
374-
const uint16_t* tileMap = (const uint16_t*)(
375-
second ? trivial_tilemap_1 : trivial_tilemap_0);
374+
const uint16_t* tileMap = frame->tileMap;
376375
uint16_t palNum =
377376
(tileMap[0] & TILE_ATTR_PALETTE_MASK) >> TILE_ATTR_PALETTE_SFT;
378377
// NOTE: We are hijacking system tiles for more space!
379378
// User tiles start at index 256, and the max index is 1425.
380-
uint16_t tileIndex = FRAME_TILE_INDEX + (second ? NUM_TILES : 0);
379+
uint16_t tileIndex = FRAME_TILE_INDEX + (second ? MAX_NUM_TILES : 0);
381380

382381
// The order of loading things here matters, but it took some experimentation
383382
// to get it right. Tiles, colors, then map gives us clean frames that look
@@ -389,7 +388,7 @@ static bool nextVideoFrame() {
389388
#pragma GCC diagnostic push
390389
#pragma GCC diagnostic ignored "-Waddress-of-packed-member"
391390
// Unpacked, raw pointer method used by VDP_loadTileSet
392-
VDP_loadTileData(frame->tiles, tileIndex, NUM_TILES, CPU);
391+
VDP_loadTileData(frame->tiles, tileIndex, frame->numTiles, CPU);
393392

394393
// Unpacked, raw pointer method used by PAL_setPaletteColors
395394
PAL_setColors(palNum << 4, frame->palette, /* count= */ 16, CPU);

0 commit comments

Comments
 (0)