Jump to content

1 Screenshot

About This File

Screenshot 2025-12-15 at 18.09.22.pngScreenshot 2025-12-15 at 18.09.06.pngScreenshot 2025-12-15 at 18.10.32.png

 

 

  • Minimum system requirement: macOS 11
  • Like 3
  • Thanks 1

User Feedback

Recommended Comments

fantomas

Posted

Hi everyone. :)

 

Here's my second project, which I started alongside CPU-X.

 

Again, it is far from perfect, so I ask your indulgence for it. ^_^

  • Like 1
fantomas

Posted

So, a little background on this project... :P
As with CPU-X, I wanted a GPU-Z equivalent for macOS. The idea for both projects was the same: to get as much "native" infos as possible via macOS. 

And for this kind of project, all the informations I could find on the net, pointed me towards implementing "dictionaries," which I didn't really want to do.  :(

So I wanted to try a more "exotic" approach: loading my GPU's ROM file to display the information that macOS couldn't. :drool:
 

Spoiler

Screenshot 2025-12-15 at 18.48.37.png

 

I really liked the idea, but the more I dug into it, the more it became complicated because, depending on the model and manufacturer, the same information could be found on the same offset or a different one, and it was very difficult to find a common offset. Maybe my approach wasn't the right one, but at the end, I just abandoned it. :cry:

So as you got it, with my poor knowledge, I had no other choice but to use a .json file, just like for CPU-X, and create a dictionary and keep it updated as needed. :whistle:

GPU-X - Right clic - Show Package Contents - Contents - Resources - gpus.json

 

You can therefore access the file and then integrate the information about your GPU if it is not recognized by the application. ;) Of course, you don't need to keep the other GPUs informations, only that of your own.

I set macOS 11 as the minimum version requirement, not necessarily knowing how it will behave on older versions (I haven't had the opportunity to test it), nor if it was relevant to do so.

 

fantomas

Posted

Oh yeah, I forgot about it. :P

 

Here a python script that will help you to get your GPU infos for .json file from TechPowerUp site

#!/usr/bin/env python3
# tpup_build_json.py
# Usage:
#   python3 tpup_build_json.py <vbios_url> <specs_url> [output.json]

import sys
import re
import json
import requests
from bs4 import BeautifulSoup

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120 Safari/537.36"
}

def fetch_vbios_ids(vbios_url):
    """Recovers subvendor + subsystem from the VBIOS page (section.cardinfo table)."""
    r = requests.get(vbios_url, headers=HEADERS, timeout=15)
    r.raise_for_status()
    soup = BeautifulSoup(r.text, "html.parser")

    # Explicitly search for <section class="cardinfo"> then <th> 'Subsystem Id'
    # Fallback: scan all <th> tags on the page if the section does not exist.
    subsystem_td_text = None
    device_td_text = None

    cardinfo = soup.find("section", class_="cardinfo")
    search_ths = cardinfo.find_all("th") if cardinfo else soup.find_all("th")
    for th in search_ths:
        key = th.get_text(strip=True).lower()
        td = th.find_next_sibling("td")
        if not td:
            continue
        val = td.get_text(" ", strip=True)
        if "subsystem" in key:  # ex: "Subsystem Id:"
            subsystem_td_text = val
        elif "device id" in key or key.startswith("device id"):
            device_td_text = val

    if not subsystem_td_text:
        raise ValueError("Unable to find 'Subsystem Id' on the VBIOS page.")

    # Expected format example: "1043 04E6" or "1043 04e6"
    parts = re.findall(r'([0-9A-Fa-f]{4})', subsystem_td_text)
    if len(parts) < 2:
        raise ValueError(f"Unable to extract subvendor/subsystem from '{subsystem_td_text}'")

    subvendor = parts[0].upper()
    subsystem = parts[1].upper()

    # optionnel: device id (ex: "1002 7340")
    device_id = None
    if device_td_text:
        dparts = re.findall(r'([0-9A-Fa-f]{4})', device_td_text)
        if len(dparts) >= 2:
            device_id = dparts[1].upper()  # usually second token = device

    return {
        "subvendor": f"0x{subvendor}",
        "subsystem": f"0x{subsystem}",
        "device_id": f"0x{device_id}" if device_id else None
    }

def find_dd_for_label(soup, label):
    """Return the texte dd corresponding to dt whose texte contains `label` (cas-insens.)."""
    label_low = label.lower()
    for dt in soup.find_all("dt"):
        dt_text = dt.get_text(" ", strip=True).lower()
        if label_low in dt_text:
            dd = dt.find_next_sibling("dd")
            if dd:
                return dd.get_text(" ", strip=True)
            # sometimes dt/dd are nested within specific dls
            parent = dt.parent
            if parent:
                dd2 = parent.find("dd")
                if dd2:
                    return dd2.get_text(" ", strip=True)
    return None

def pick_mhz_from_text(txt):
    """Extracts the last MHz value found in txt (ex: '...1607 MHz...1647 MHz (+2%)' -> 1647)."""
    if not txt:
        return None
    nums = re.findall(r'(\d+)\s*MHz', txt, flags=re.I)
    if not nums:
        return None
    return int(nums[-1])

def convert_memory_to_mb(txt):
    """Convert '8 GB' -> 8192, '4096 MB' -> 4096. Return full MB ou original string if impossible."""
    if not txt:
        return None
    m = re.search(r'([0-9,.]+)\s*(GB|MB)', txt, flags=re.I)
    if not m:
        # maybe '8192 MB' directly
        m2 = re.search(r'(\d+)\s*MB', txt, flags=re.I)
        if m2:
            return int(m2.group(1))
        return None
    val = float(m.group(1).replace(',', ''))
    unit = m.group(2).upper()
    if unit == "GB":
        return int(val * 1024)
    else:
        return int(val)

def fetch_specs(specs_url):
    """Recovers the relevant fields from the GPU specs page."""
    r = requests.get(specs_url, headers=HEADERS, timeout=15)
    r.raise_for_status()
    soup = BeautifulSoup(r.text, "html.parser")

    # Model name (h1.gpudb-name)
    title = ""
    h1 = soup.find("h1", class_="gpudb-name")
    if h1:
        title = h1.get_text(" ", strip=True)
    else:
        # fallback: title tag
        title = soup.title.string.strip() if soup.title else ""

    # Fields to extract (tolerant of label variations)
    gpu = find_dd_for_label(soup, "Graphics Processor") or find_dd_for_label(soup, "GPU Name")
    cores = find_dd_for_label(soup, "Cores") or find_dd_for_label(soup, "Shading Units") or find_dd_for_label(soup, "Shaders")
    tmus = find_dd_for_label(soup, "TMUs")
    rops = find_dd_for_label(soup, "ROPs")
    mem_size = find_dd_for_label(soup, "Memory Size")
    mem_type = find_dd_for_label(soup, "Memory Type")
    mem_bw = find_dd_for_label(soup, "Bandwidth") or find_dd_for_label(soup, "Memory Bandwidth")
    tdp = find_dd_for_label(soup, "TDP")
    release = find_dd_for_label(soup, "Release Date")
    die = find_dd_for_label(soup, "Die Size")
    trans = find_dd_for_label(soup, "Transistors")
    process = find_dd_for_label(soup, "Process Size")
    pixel = find_dd_for_label(soup, "Pixel Rate")
    texture = find_dd_for_label(soup, "Texture Rate")

    # Clocks (section "Clock Speeds" contains dt/dd pairs)
    base_clock_text = find_dd_for_label(soup, "Base Clock")
    game_clock_text = find_dd_for_label(soup, "Game Clock")
    boost_clock_text = find_dd_for_label(soup, "Boost Clock")
    memory_clock_text = find_dd_for_label(soup, "Memory Clock")

    base_clock = pick_mhz_from_text(base_clock_text)
    game_clock = pick_mhz_from_text(game_clock_text)
    boost_clock = pick_mhz_from_text(boost_clock_text)
    memory_clock = pick_mhz_from_text(memory_clock_text)

    mem_mb = convert_memory_to_mb(mem_size) if mem_size else None

    out = {
        "Model Name": title,
        "GPU": gpu,
        "Shaders": None,
        "TMUs": None,
        "ROPs": None,
        "Memory Size": None,
        "Memory Type": mem_type,
        "Base Clock": None,
        "Game Clock": None,
        "Boost Clock": None,
        "Memory Clock": None,
        "Memory Bandwidth": mem_bw,
        "TDP": tdp,
        "Release Date": release,
        "Die Size": die,
        "Transistors": trans,
        "Process Size": process,
        "Pixel Rate": pixel,
        "Texture Rate": texture
    }

    # normalize numeric-like fields
    def try_int(s):
        if not s: return None
        m = re.search(r'(\d+)', s.replace(',', ''))
        return int(m.group(1)) if m else None

    # Shaders/cores may be a direct number
    if cores:
        out["Shaders"] = try_int(cores)

    if tmus:
        out["TMUs"] = try_int(tmus)

    if rops:
        out["ROPs"] = try_int(rops)

    if mem_mb:
        out["Memory Size"] = mem_mb
    else:
        # fallback: try to parse mem_size directly if already MB string
        maybe = convert_memory_to_mb(mem_size) if mem_size else None
        out["Memory Size"] = maybe

    if base_clock:
        out["Base Clock"] = f"{base_clock} MHz"
    if game_clock:
        out["Game Clock"] = f"{game_clock} MHz"
    if boost_clock:
        out["Boost Clock"] = f"{boost_clock} MHz"
    if memory_clock:
        out["Memory Clock"] = f"{memory_clock} MHz"

    # Keep original memory type if present
    if mem_type:
        out["Memory Type"] = mem_type

    return out

def build_json(vbios_url, specs_url):
    ids = fetch_vbios_ids(vbios_url)
    specs = fetch_specs(specs_url)

    key = f"{ids['subvendor']}:{ids['subsystem']}"
    entry = specs.copy()
    # add device id if available
    if ids.get("device_id"):
        entry["Device ID"] = ids["device_id"]
    entry["Source VBIOS"] = vbios_url
    entry["Source Specs"] = specs_url

    return { key: entry }

def main(argv):
    if len(argv) < 3:
        print("Usage: tpup_build_json.py <vbios_url> <specs_url> [output.json]")
        return 2

    vbios_url = argv[1]
    specs_url = argv[2]
    out_file = argv[3] if len(argv) >= 4 else None

    try:
        print("Fetching VBIOS ids...")
        result = build_json(vbios_url, specs_url)
    except Exception as e:
        print("ERROR:", e)
        return 1

    if out_file:
        # try to merge into existing file if exists
        try:
            current = {}
            with open(out_file, "r", encoding="utf-8") as f:
                current = json.load(f)
        except Exception:
            current = {}
        current.update(result)
        with open(out_file, "w", encoding="utf-8") as f:
            json.dump(current, f, indent=2, ensure_ascii=False)
        print(f"Saved JSON into {out_file}")
    else:
        print(json.dumps(result, indent=2, ensure_ascii=False))

    return 0

if __name__ == "__main__":
    sys.exit(main(sys.argv))

 

an example for my GPU: ASUS DUAL RX 5500 XT EVO OC

python3 tpup_build_json.py https://www.techpowerup.com/vgabios/209828/gigabyte-rx590-8192-190218 https://www.techpowerup.com/gpu-specs/gigabyte-rx-590-gaming-rev-1-0.b6784 output.json

 

SavageAUS

Posted

Seems to work ok, missing some information

Spoiler

Screenshot2026-04-18at8_06_43PM.png.6bdc3e7435e8bf1d124c45ded23e7838.png

 

  • Thanks 1
×
×
  • Create New...