fantomas 5142 Posted December 15, 2025 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. 1
fantomas 5142 Posted December 15, 2025 So, a little background on this project... 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. Spoiler 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. 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. 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 5142 Posted December 15, 2025 Oh yeah, I forgot about it. 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
fantomas 5142 Posted April 18 @SavageAUS Thnks for your report Yes, you can use the .json file to add missing features. GPU-X - Right clic - Show Package Contents - Contents - Resources - gpus.json --Edit-- Is this your gpu? https://www.techpowerup.com/vgabios/230881/powercolor-rx6800xt-16384-201206
Recommended Comments