diff --git a/Texture.py b/Texture.py
index daeda77..1b56d1b 100644
--- a/Texture.py
+++ b/Texture.py
@@ -54,6 +54,30 @@
X8R8G8B8 = TextureDescription(32, (8, 8, 8), (16, 8, 0))
+def f16_to_float(f):
+ if f == 0x0:
+ return 0.0
+ f = (f << 11) + 0x3C000000
+ return struct.unpack("f", f.to_bytes(4, "little"))[0]
+
+
+def f24_to_float(f):
+ assert (f >> 24) == 0
+ f &= 0xFFFFFF
+ if f == 0x0:
+ return 0.0
+ f = f << 7
+ return struct.unpack("f", f.to_bytes(4, "little"))[0]
+
+
+ZetaDescription = namedtuple(
+ "ZetaDescription",
+ ["depth_mask", "depth_shift", "stencil_mask", "bpp", "convert_float"],
+)
+Z24S8 = ZetaDescription(0xFFFFFF00, 8, 0xFF, 32, f24_to_float)
+Z16 = ZetaDescription(0xFFFF, 0, 0, 16, f16_to_float)
+
+
def _decode_texture(
data, size, pitch, swizzled, bits_per_pixel, channel_sizes, channel_offsets
):
@@ -182,6 +206,8 @@ def read_texture_parameters(xbox: Xbox) -> TextureParameters:
swizzle_unk2 = xbox.read_u32(0xFD40086C)
+ setup_raster = xbox.read_u32(0xFD401990)
+
clip_x = (surface_clip_x >> 0) & 0xFFFF
clip_y = (surface_clip_y >> 0) & 0xFFFF
@@ -208,15 +234,18 @@ def read_texture_parameters(xbox: Xbox) -> TextureParameters:
# FIXME: if surface_type is 0, we probably can't even draw..
format_color = (draw_format >> 12) & 0xF
- # FIXME: Support 3D surfaces.
- _format_depth_buffer = (draw_format >> 18) & 0x3
+
+ z_float = (setup_raster >> 29) & 0x1
+ format_depth_buffer = (draw_format >> 18) & 0x3
if not format_color:
fmt_color = None
else:
fmt_color = surface_color_format_to_texture_format(format_color, swizzled)
- # TODO: Extract swizzle and float state.
- # fmt_depth = surface_zeta_format_to_texture_format(format_depth_buffer)
+
+ fmt_depth = surface_zeta_format_to_texture_format(
+ format_depth_buffer, swizzled, z_float
+ )
return TextureParameters(
width=width,
@@ -226,7 +255,7 @@ def read_texture_parameters(xbox: Xbox) -> TextureParameters:
format_color=fmt_color,
depth_pitch=depth_pitch,
depth_offset=depth_offset,
- format_depth=None,
+ format_depth=fmt_depth,
surface_type=surface_type,
swizzle_unk=swizzle_unk,
swizzle_unk2=swizzle_unk2,
@@ -234,6 +263,93 @@ def read_texture_parameters(xbox: Xbox) -> TextureParameters:
)
+def dump_zeta(data, offset, pitch, fmt_depth, width, height):
+ """Convert the zeta buffer at the given offset into a PIL.Image."""
+ depth_img = Image.new("RGB", (width, height))
+ stencil_img = None
+
+ if fmt_depth == 0x2D:
+ info = (True, True, Z16)
+ elif fmt_depth == 0x31:
+ info = (False, True, Z16)
+ elif fmt_depth == 0x2C:
+ info = (True, False, Z16)
+ elif fmt_depth == 0x30:
+ info = (False, False, Z16)
+ elif fmt_depth == 0x2B:
+ info = (True, True, Z24S8)
+ stencil_img = Image.new("RGB", (width, height))
+ elif fmt_depth == 0x2F:
+ info = (False, True, Z24S8)
+ stencil_img = Image.new("RGB", (width, height))
+ elif fmt_depth == 0x2A:
+ info = (True, False, Z24S8)
+ stencil_img = Image.new("RGB", (width, height))
+ elif fmt_depth == 0x2E:
+ info = (False, False, Z24S8)
+ stencil_img = Image.new("RGB", (width, height))
+ else:
+ raise Exception("Unknown depth format :0x%X" % fmt_depth)
+
+ swizzle = info[0]
+ is_float = info[1]
+ fmt = info[2]
+
+ # FIXME: Avoid this nasty ~~convience feature~~ hack
+ if pitch == 0:
+ pitch = width * fmt.bpp // 8
+
+ assert len(data) == pitch * height
+
+ # FIXME: Does this work?
+ if False and swizzle:
+ data = xbox.Unswizzle(data, fmt.bpp, (width, height), pitch)
+
+ depth_values = {}
+ depth_max = 0
+ depth_min = 0xFFFFFF
+ stencil_values = {}
+
+ for y in range(height):
+ for x in range(width):
+ pixel_offset = y * pitch + x * fmt.bpp // 8
+ z_data = data[pixel_offset : pixel_offset + fmt.bpp // 8]
+ zeta = int.from_bytes(z_data, "little")
+ depth = (zeta & fmt.depth_mask) >> fmt.depth_shift
+ stencil = zeta & fmt.stencil_mask
+
+ if is_float:
+ depth = fmt.convert_float(depth)
+
+ depth_values[x, y] = depth
+ stencil_values[x, y] = 255 if stencil != 0 else 0
+
+ if depth > depth_max:
+ depth_max = depth
+
+ if depth < depth_min:
+ depth_min = depth
+
+ # Map depth values to [0, 255]
+ assert depth_max >= depth_min
+ depth_max -= depth_min
+ depth_scaler = 255 / depth_max if depth_max > 0 else 255
+ depth_pixels = depth_img.load()
+ if stencil_img is not None:
+ stencil_pixels = stencil_img.load()
+
+ for y in range(height):
+ for x in range(width):
+ depth = int((depth_values[x, y] - depth_min) * depth_scaler)
+ stencil = stencil_values[x, y]
+
+ depth_pixels[x, y] = (depth, depth, depth)
+ if stencil_img is not None:
+ stencil_pixels[x, y] = (stencil, stencil, stencil)
+
+ return (depth_img, stencil_img)
+
+
def dump_texture(xbox, offset, pitch, fmt_color, width, height):
"""Convert the texture at the given offset into a PIL.Image."""
img = None
diff --git a/Trace.py b/Trace.py
index f4fbdb0..9a626b7 100644
--- a/Trace.py
+++ b/Trace.py
@@ -420,6 +420,12 @@ def dump_surfaces(self, _data, *_args):
print("Warning: Invalid color format, skipping surface dump.")
return []
+ if params.depth_offset:
+ depth_buffer = self.xbox.read(
+ Texture.AGP_MEMORY_BASE | params.depth_offset,
+ params.depth_pitch * params.height,
+ )
+
# Dump stuff we might care about
self._write("pgraph.bin", _dump_pgraph(self.xbox))
self._write("pfb.bin", _dump_pfb(self.xbox))
@@ -434,10 +440,7 @@ def dump_surfaces(self, _data, *_args):
if params.depth_offset and self.enable_raw_pixel_dumping:
self._write(
"mem-3.bin",
- self.xbox.read(
- Texture.AGP_MEMORY_BASE | params.depth_offset,
- params.depth_pitch * params.height,
- ),
+ depth_buffer,
)
if self.enable_rdi:
self._write(
@@ -473,7 +476,6 @@ def dump_surfaces(self, _data, *_args):
else:
alpha_path = None
- path = "command%d--color.png" % (self.command_count)
extra_html = []
extra_html += [img_tags]
@@ -516,6 +518,53 @@ def dump_surfaces(self, _data, *_args):
self._save_image(img, no_alpha_path, alpha_path)
+ depth_path = "command%d--depth.png" % (self.command_count)
+ stencil_path = "command%d--stencil.png" % (self.command_count)
+
+ try:
+ if not params.depth_offset:
+ raise Exception("Depth offset is null")
+
+ self._dbg_print("Attempting to dump zeta")
+ depth, stencil = Texture.dump_zeta(
+ depth_buffer,
+ params.depth_offset,
+ params.depth_pitch,
+ params.format_depth,
+ params.width,
+ params.height,
+ )
+ except:
+ depth = None
+ stencil = None
+ print("Failed to dump zeta surface")
+ traceback.print_exc()
+
+ self._save_image(depth, None, depth_path)
+ self._save_image(stencil, None, stencil_path)
+
+ zeta_img_tags = '
' % (
+ depth_path,
+ depth_path,
+ )
+
+ if stencil is not None:
+ zeta_img_tags += '
' % (
+ stencil_path,
+ stencil_path,
+ )
+
+ extra_html += [zeta_img_tags]
+ extra_html += [
+ "zeta: [pitch = %d (0x%X)], at 0x%08X, format 0x%X]"
+ % (
+ params.depth_pitch,
+ params.depth_pitch,
+ params.depth_offset,
+ params.format_depth,
+ )
+ ]
+
return extra_html
def _save_image(self, img, no_alpha_path, alpha_path):