KyoMel / Yogyakarta, Indonesia

Term 253253 / 344

Frame Buffer Hashing

Month
June 2026
Day
10
Source
2026/june-term.md

Definition:

Frame Buffer Hashing is a visual regression testing technique that verifies rendered graphics by reading the raw GPU framebuffer (the pixel data before any compression or encoding), computing a cryptographic hash of those bytes, and comparing it against a known-good reference hash. If the hashes match, every pixel is identical. If they don’t, something changed.

In one sentence: “Take a fingerprint of the raw pixels straight from the GPU, then compare it to the golden fingerprint — if they match, the render is perfect.”

Analogy — The Painting Authenticator Imagine an art authenticator verifying that a famous painting hasn’t been tampered with. She has two approaches:

Approach What happens Result 📸 Take a photo, then compare photos Photo compression introduces artifacts. Two identical paintings shot with different camera settings produce different JPEGs. “They don’t match!” → false alarm 😤 🔬 Scan the canvas directly She places a high-res scanner directly on the original canvas, captures raw pigment data, and hashes it. Same canvas = same hash. Always. “Hash matches. It’s authentic.” ✅ The photo is like a PNG screenshot — encoded, compressed, lossy. The direct scan is like the raw framebuffer — pure, unmodified pixel data from the GPU.

Example:

Frame Buffer Hashing in Practice

Here's a simplified version based on real GPU testing frameworks (Intel GPU Tools, Chromium's video frame validator):

import hashlib
import struct
from dataclasses import dataclass
from typing import Dict

@dataclass
class FrameBuffer:
    """Represents a raw GPU framebuffer mapped to CPU memory."""
    width: int
    height: int
    bytes_per_pixel: int  # e.g., 4 for RGBA
    raw_bytes: bytes       # raw pixel data straight from the GPU

def hash_framebuffer(fb: FrameBuffer) -> str:
    """
    Compute an MD5 hash of the raw framebuffer bytes.
    No encoding, no compression — pure pixel data.
    """
    # In real GPU testing, you'd map the framebuffer from GPU memory:
    #   ptr = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY)
    #   fb.raw_bytes = ptr[0 : width * height * bpp]

    return hashlib.md5(fb.raw_bytes).hexdigest()

# ── Golden Reference Store ──
# Stored once when the test is first created.
# A few KB for hundreds of frames (vs. GBs of PNGs).
GOLDEN_HASHES: Dict[str, str] = {
    "main_menu":        "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
    "settings_screen":  "1f2e3d4c5b6a79808182838485868788",
    "gameplay_frame_1": "deadbeefcafebabedeadbeefcafebabe",
}

def visual_regression_test(scene_name: str, fb: FrameBuffer) -> bool:
    """
    Run a single visual regression test.
    Returns True if the frame matches the golden reference.
    """
    actual_hash = hash_framebuffer(fb)
    expected_hash = GOLDEN_HASHES.get(scene_name)

    if expected_hash is None:
        # New scene — store this as the golden reference
        print(f"✨ No golden hash for '{scene_name}'. Storing: {actual_hash}")
        GOLDEN_HASHES[scene_name] = actual_hash
        return True

    if actual_hash == expected_hash:
        print(f"✅ {scene_name}: PASS (hash matches)")
        return True
    else:
        print(f"❌ {scene_name}: FAIL")
        print(f"   Expected: {expected_hash}")
        print(f"   Got:      {actual_hash}")
        # Save the actual frame as a PNG for human review
        save_frame_as_png(fb, f"failure_artifacts/{scene_name}.png")
        return False

def save_frame_as_png(fb: FrameBuffer, path: str):
    """Only save a PNG when the test FAILS — for manual inspection."""
    # Convert raw bytes to PNG (using PIL, stb_image, etc.)
    # This is the ONLY time encoding happens — after failure.
    print(f"   📸 Saved failure artifact: {path}")

# ── Usage ──
# Simulate a correct render
correct_frame = FrameBuffer(
    width=1920, height=1080, bytes_per_pixel=4,
    raw_bytes=b'\xff\x00\x00\xff' * (1920 * 1080)  # all red pixels
)
test1 = visual_regression_test("main_menu", correct_frame)

# Simulate a rendering bug — one pixel is wrong
buggy_bytes = bytearray(b'\xff\x00\x00\xff' * (1920 * 1080))
buggy_bytes[0] = 0xfe  # slightly different red in first pixel
buggy_frame = FrameBuffer(
    width=1920, height=1080, bytes_per_pixel=4,
    raw_bytes=bytes(buggy_bytes)
)
test2 = visual_regression_test("main_menu", buggy_frame)
Output:

✅ main_menu: PASS (hash matches)
❌ main_menu: FAIL
   Expected: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
   Got:      7f8e9d0c1b2a39485748392a1b0c0d0e
   📸 Saved failure artifact: failure_artifacts/main_menu.png

Keep reading