From 0fffccad41bb41aad14fc243375794c1d905bfbc Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Tue, 11 Feb 2025 22:47:55 -0600 Subject: [PATCH] add renderer --- isp_tsp_parameter.py | 170 +++++++++++++++++++++++++++++++++++++ main.py | 195 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 340 insertions(+), 25 deletions(-) create mode 100644 isp_tsp_parameter.py diff --git a/isp_tsp_parameter.py b/isp_tsp_parameter.py new file mode 100644 index 0000000..f7405d2 --- /dev/null +++ b/isp_tsp_parameter.py @@ -0,0 +1,170 @@ +from dataclasses import dataclass +from functools import partial +import struct + +@dataclass +class IspTspInstructionWordMV: + volume_instruction: int + culling_mode: int + + def __init__(self, value): + assert (value & 0x7ffffff) == 0 + self.volume_instruction = (value >> 29) & 0b111 + self.culling_mode = (value >> 27) & 0b11 + +@dataclass +class IspTspInstructionWord: + depth_compare_mode: int + culling_mode: int + z_write_disable: int + texture: int + offset: int + gouraud_shading: int + _16bit_uv: int + cache_bypass: int + dcalc_ctrl: int + + def __init__(self, value): + assert (value & 0xfffff) == 0 + self.depth_compare_mode = (value >> 29) & 0b111 + self.culling_mode = (value >> 27) & 0b11 + self.z_write_disable = (value >> 26) & 1 + self.texture = (value >> 25) & 1 + self.offset = (value >> 24) & 1 + self.gouraud_shading = (value >> 23) & 1 + self._16bit_uv = (value >> 22) & 1 + self.cache_bypass = (value >> 21) & 1 + self.dcalc_ctrl = (value >> 20) & 1 + +@dataclass +class TspInstructionWord: + src_alpha_instr: int + dst_alpha_instr: int + src_select: int + dst_select: int + fog_control: int + color_clamp: int + use_alpha: int + ignore_texture_alpha: int + flip_uv: int + clamp_uv: int + filter_mode: int + super_sample_texture: int + mip_map_d_adjust: int + texture_shading_instruction: int + texture_u_size: int + texture_v_size: int + + def __init__(self, value): + self.src_alpha_instr = (value >> 29) & 0b111 + self.dst_alpha_instr = (value >> 26) & 0b111 + self.src_select = (value >> 25) & 1 + self.dst_select = (value >> 24) & 1 + self.fog_control = (value >> 22) & 0b11 + self.color_clamp = (value >> 21) & 1 + self.use_alpha = (value >> 20) & 1 + self.ignore_texture_alpha = (value >> 19) & 1 + self.flip_uv = (value >> 17) & 0b11 + self.clamp_uv = (value >> 15) & 0b11 + self.filter_mode = (value >> 13) & 0b11 + self.super_sample_texture = (value >> 12) & 1 + self.mip_map_d_adjust = (value >> 8) & 0b1111 + self.texture_shading_instruction = (value >> 6) & 0b11 + self.texture_u_size = (value >> 3) & 0b111 + self.texture_v_size = (value >> 0) & 0b111 + +@dataclass +class TextureControlWord: + mip_mapped: int + vq_compressed: int + pixel_format: int + scan_order: int + stride_select: int + texture_address: int + + def __init__(self, value): + assert (value >> 21) & 0b1111 == 0 + + self.mip_mapped = (value >> 31) & 1 + self.vq_compressed = (value >> 30) & 1 + self.pixel_format = (value >> 27) & 0b111 + self.scan_order = (value >> 26) & 1 + self.stride_select = (value >> 25) & 1 + self.texture_address = ((value >> 0) & 0x1fffff) * 8 + +@dataclass +class Vertex: + x: float + y: float + z: float + attributes: list + +@dataclass +class ISPTSPParameter: + isp_tsp_instruction_word: IspTspInstructionWord + tsp_instruction_word: TspInstructionWord + texture_control_word: TextureControlWord + vertices: list[Vertex] + +@dataclass +class ISPTSPParameterMV: + isp_tsp_instruction_word: IspTspInstructionWord + vertices: list[Vertex] + +def expected_skip_size(isp_tsp): + pass + +def parse_parameter(mem, offset, skip, tag_offset, vertex_count, modifier_volume): + assert skip >= 0, skip + skip = (skip + 3) # size of one vertex + + params = [] + + def unpack(i, t): + i_offset = offset + i * 4 + value, = struct.unpack("<" + t, mem[i_offset:i_offset + 4]) + return value + + unpack_int = partial(unpack, t="I") + unpack_float = partial(unpack, t="f") + + if modifier_volume: + assert skip == 3, skip + parameter = ISPTSPParameterMV( + IspTspInstructionWordMV(unpack_int(0)), + vertices=[] + ) + value1 = unpack_int(1) + assert value1 == 0, value1 + value2 = unpack_int(2) + assert value2 == 0, value2 + else: + parameter = ISPTSPParameter( + IspTspInstructionWord(unpack_int(0)), + TspInstructionWord(unpack_int(1)), + TextureControlWord(unpack_int(2)), + vertices=[] + ) + + + assert tag_offset == 0, tag_offset # FIXME + + i = 3 + for _ in range(vertex_count): + vertex = Vertex( + x = unpack_float(i + 0), + y = unpack_float(i + 1), + z = unpack_float(i + 2), + attributes=[] + ) + + j = 3 + while j < skip: + vertex.attributes.append(unpack_int(i + j)) + j += 1 + + parameter.vertices.append(vertex) + + i += skip + + return offset + i * 4, parameter diff --git a/main.py b/main.py index 3131a11..96dc47f 100644 --- a/main.py +++ b/main.py @@ -1,9 +1,17 @@ -from pprint import pprint +import os +os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide" +import textwrap +from pprint import pprint, pformat import sys +import pygame +from functools import partial +from itertools import starmap +import time from holly import decode_holly from region_array import decode_region_array_entries from object_list import * +from isp_tsp_parameter import parse_parameter texture_memory_filename = sys.argv[1] holly_registers_filename = sys.argv[2] @@ -20,28 +28,165 @@ assert (holly.REGION_BASE & 0b11) == 0, holly.REGION_BASE region_array_entries = decode_region_array_entries(texture_memory, holly.REGION_BASE) -for entry in region_array_entries: - lists = [ - ("opaque_list_pointer", entry.opaque_list_pointer), - ("opaque_modifier_volume_list_pointer", entry.opaque_modifier_volume_list_pointer), - ("translucent_list_pointer", entry.translucent_list_pointer), - ("translucent_modifier_volume_list_pointer", entry.translucent_modifier_volume_list_pointer), - ("punch_through_list_pointer", entry.punch_through_list_pointer), +DEBUG = False + +def walk_region_array(*, tile_callback=None, parameter_callback=None, flush_callback=None): + for entry in region_array_entries: + lists = [ + ("opaque_list_pointer", entry.opaque_list_pointer), + ("opaque_modifier_volume_list_pointer", entry.opaque_modifier_volume_list_pointer), + ("translucent_list_pointer", entry.translucent_list_pointer), + ("translucent_modifier_volume_list_pointer", entry.translucent_modifier_volume_list_pointer), + ("punch_through_list_pointer", entry.punch_through_list_pointer), + ] + if tile_callback is not None: + tile_callback(entry.tile) + + for list_type_name, list_pointer in lists: + if DEBUG: + print(" " * 4, end='') + print(list_type_name, list_pointer) + if list_pointer.empty_ptr: + continue + offset = list_pointer.pointer_to_object_list + while True: + ol_entry = decode_ol_entry(texture_memory, offset) + if DEBUG: + print(" " * 8, end='') + print(ol_entry) + offset += 4 + if type(ol_entry) == triangle_array: + param_offset = ol_entry.triangle_array_start + for i in range(ol_entry.number_of_triangles + 1): + modifier_volume = list_type_name in {"opaque_modifier_volume_list_pointer", "translucent_modifier_volume_list_pointer"} + param_offset, parameter = parse_parameter(texture_memory, param_offset, + skip=ol_entry.skip, + tag_offset=0, + vertex_count=3, + modifier_volume=modifier_volume) + + if parameter_callback is not None: + parameter_callback(entry.tile, list_type_name, list_pointer, ol_entry, parameter) + + if DEBUG: + print(textwrap.indent( + pformat(parameter), + " " * 12 + )) + + if type(ol_entry) == object_pointer_block_link: + if ol_entry.end_of_list: + break + else: + offset = ol_entry.next_pointer_block + + if entry.tile.flush_accumulate == 0: + if flush_callback is not None: + flush_callback(entry.tile) + +def new_tile_surface(): + surface = pygame.Surface((32, 32), pygame.SRCALPHA) + surface.fill((0, 0, 0, 0)) + return surface + +x_tiles = (640 // 32) +y_tiles = (480 // 32) + +color_buffer_list = [ + new_tile_surface() + for _ in range(x_tiles * y_tiles) +] + +depth_buffer_list = [ + new_tile_surface() + for _ in range(x_tiles * y_tiles) +] + +color_test_surface = new_tile_surface() +depth_test_surface = new_tile_surface() + +def draw_background(tile): + t = holly.ISP_BACKGND_T + skip = (t >> 24) & 0b111 + tag_address = ((t >> 3) & 0x1fffff) * 4 + + param_offset, parameter = parse_parameter(texture_memory, tag_address, + skip=skip, + tag_offset=0, + vertex_count=3, + modifier_volume=False) + assert len(parameter.vertices) == 3, parameter.vertices + assert len(parameter.vertices[0].attributes) >= 1, parameter + argb_color = parameter.vertices[0].attributes[0] + alpha = (argb_color >> 24) & 0xff + red = (argb_color >> 16) & 0xff + green = (argb_color >> 8) & 0xff + blue = (argb_color >> 0) & 0xff + color = (red, green, blue, alpha) + + points = [(0, 0), (640, 0), (640, 480), (0, 480)] + + tile_ix = tile.tile_y_position * x_tiles + tile.tile_x_position + color_buffer = color_buffer_list[tile_ix] + pygame.draw.polygon(color_buffer, color, points) + +def tile_callback(tile): + draw_background(tile) + +def flush_callback(tile, *, screen): + tile_ix = tile.tile_y_position * x_tiles + tile.tile_x_position + color_buffer = color_buffer_list[tile_ix] + screen.blit(color_buffer, (tile.tile_x_position * 32, tile.tile_y_position * 32)) + pygame.display.flip() + + time.sleep(0.1) + +def parameter_callback(tile, list_type_name, list_pointer, ol_entry, parameter): + # tile=RegionArrayTile(last_region=1, z_clear=0, flush_accumulate=0, tile_y_position=14, tile_x_position=19) + if list_type_name != "opaque_list_pointer": + return + + left = tile.tile_x_position * 32 + top = tile.tile_y_position * 32 + + tile_ix = tile.tile_y_position * x_tiles + tile.tile_x_position + color_buffer = color_buffer_list[tile_ix] + + assert type(ol_entry) == triangle_array, ol_entry + assert len(parameter.vertices) == 3, parameter.vertices + assert len(parameter.vertices[0].attributes) >= 1, parameter + + points = [ + (vertex.x - left, vertex.y - top) + for vertex in parameter.vertices ] - print(entry.tile) - for list_type_name, list_pointer in lists: - print(" " * 4, end='') - print(list_type_name, list_pointer) - if list_pointer.empty_ptr: - continue - offset = list_pointer.pointer_to_object_list - while True: - ol_entry = decode_ol_entry(texture_memory, offset) - print(" " * 8, end='') - print(ol_entry) - offset += 4 - if type(ol_entry) == object_pointer_block_link: - if ol_entry.end_of_list: - break - else: - offset = ol_entry.next_pointer_block + + argb_color = parameter.vertices[0].attributes[0] + alpha = (argb_color >> 24) & 0xff + red = (argb_color >> 16) & 0xff + green = (argb_color >> 8) & 0xff + blue = (argb_color >> 0) & 0xff + color = (red, green, blue, alpha) + + assert red > 10 or blue > 10 or green > 10, argb_color + + pygame.draw.polygon(color_buffer, color, points) + +def draw(): + pygame.init() + screen = pygame.display.set_mode((640, 480)) + + sys.stdin.readline() + + walk_region_array(tile_callback=tile_callback, + parameter_callback=parameter_callback, + flush_callback=partial(flush_callback, screen=screen)) + + while True: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + break + + pygame.quit() + +draw()