Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert depth buffer to images #41

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 121 additions & 5 deletions Texture.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,30 @@
X8R8G8B8 = TextureDescription(32, (8, 8, 8), (16, 8, 0))


def f16_to_float(f):
if f == 0x0:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: if not f: would be more pythonic (here and below)

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
):
Expand Down Expand Up @@ -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

Expand All @@ -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,
Expand All @@ -226,14 +255,101 @@ 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,
swizzled=swizzled,
)


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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use symbolic names for these rather than the raw values?

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?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's just remove this since it's never invoked.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think python implementations do a superb job of optimizing, I'd extract out some of these values that don't change.

E.g., move the y * pitch out of the x loop, move fmt.bpp // 8 out of the loops entirely

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I think you can drop the explicit != 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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I don't think you need to explicitly check against None here, just if stencil_img would be more pythonic since anything falsey is not going to be callable.

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
Expand Down
59 changes: 54 additions & 5 deletions Trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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(
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: Depending on whether this goes in before or after #42 we'll want to expand this path to capture the draw count and depth_offset

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 = '<img height="128px" src="%s" alt="%s"/>' % (
depth_path,
depth_path,
)

if stencil is not None:
zeta_img_tags += '<img height="128px" src="%s" alt="%s"/>' % (
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):
Expand Down