Source browser
scripts/generate_api.py
Read the complete file below or download its original bytes. Line numbers provide stable links to a declaration or processing step.
File fingerprint
1#!/usr/bin/env python3
2"""Render public runtime declarations as a searchable HTML reference.
3
4The reference includes syntax-highlighted signatures and linked header source.
5"""
6
7from __future__ import annotations # Keep type hints usable on older supported Python versions.
8
9import argparse # Read command-line options and show help.
10import html # Escape source text before placing it in HTML.
11from pathlib import Path # Build paths and read or write files.
12import re # Find declarations, comments and diagnostic lines.
13from highlight_docs import add_stylesheet, highlight_code, highlight_lines # Render static syntax colors and preserve source text.
14
15
16ROOT = Path(__file__).resolve().parent.parent # Find the checkout containing the maintained source files.
17GROUPS = ( # List the API sections in their displayed order.
18 "Build integration and shader exports", # Group device setup and shader export helpers.
19 "Resources and synchronization", # Group shared buffers, images and transfer methods.
20 "Kernels and dispatch", # Group kernel interfaces and dispatch dimensions.
21 "Pipelines and dynamic libraries", # Group linked stages and kernel factories.
22 "Asynchronous execution", # Group fence handles and CPU work queues.
23 "Headless EGL backend", # Group optional context and headless device creation.
24 "GLSL math", # Group portable vector, matrix and numeric functions.
25 "C++ adapter declaration macros", # Group the shader interface declaration macros.
26 "Other documented declarations", # Keep remaining public helpers discoverable.
27) # Keep this display order when collecting rendered API sections.
28
29
30def clean_comment(raw: str) -> str: # Remove C++ comment prefixes before rendering their text.
31 """Strip comment decoration while preserving paragraph boundaries."""
32 raw = re.sub(r"^/\*\*|\*/$", "", raw.strip()) # Remove the outer comment markers.
33 return "\n".join( # Return comment text with its paragraph boundaries preserved.
34 re.sub(r"^\s*(?:\*\s?|///\s?)", "", line).rstrip() for line in raw.splitlines() # Remove decoration from each comment line.
35 ).strip() # Remove whitespace around the complete cleaned docstring.
36
37
38def declaration_after(source: str, end: int) -> str: # Read the C++ declaration immediately following a docstring.
39 """Read a declaration, omitting inline bodies and multiline macro bodies."""
40 remainder = source[end:].lstrip() # Read the text following this declaration or field.
41 if not remainder or remainder.startswith(("/**", "///")): # Select comments attached to a declaration.
42 return "" # Return an empty signature for this unattached comment.
43 if remainder.startswith("#define"): # Show the macro name and parameters as its signature.
44 macro = re.match(r"#define\s+\w+(?:\([^)]*\))?", remainder) # Recognize a generated macro or declaration name.
45 return macro.group() if macro else remainder.splitlines()[0] # Keep the macro name and parameters, or its first line as a fallback.
46 if re.match(r"enum(?:\s+class)?\s+\w+", remainder): # Keep the complete value list when the documented declaration is an enum.
47 end = remainder.find(";") # Locate the declaration or directive end.
48 if 0 <= end < 800: # Accept a nearby enum terminator while avoiding an unrelated later declaration.
49 return remainder[: end + 1] # Include the enum declaration through its final semicolon.
50 lines = [] # Collect declaration signature lines.
51 for line in remainder.splitlines()[:14]: # Limit the search to fourteen lines after the docstring.
52 line = line.split("//", 1)[0].rstrip() # Remove inline C++ comments from the displayed signature.
53 found = re.search(r"[;{]", line) # Locate the first declaration terminator on this line.
54 if found: # Stop at a declaration terminator or the opening of an inline function body.
55 lines.append(line[: found.start()] + (";" if line[found.start()] == ";" else "")) # Keep this line in the displayed C++ signature.
56 break # Stop once the declaration boundary has been found.
57 if line.strip(): # Retain nonempty signature lines before that boundary.
58 lines.append(line) # Keep this line in the displayed C++ signature.
59 return "\n".join(lines).strip() # Return the collected signature with its original line breaks.
60
61
62def choose_group(declaration: str, comment: str, position: int, source: str) -> str: # Place each API entry in a useful documentation section.
63 """Group public types and functions, with a GLSL namespace fallback."""
64 combined = declaration + " " + comment # Search both the signature and its explanation.
65 if re.match(r"#define\s+SHDR_VERSION(?:_|\b)", declaration): # Keep release identification with the basic build and setup helpers.
66 return GROUPS[0] # Show version macros before the shader declaration interface.
67 if declaration.startswith("#define"): # Keep declaration macros together even when their prose mentions resources.
68 return GROUPS[7] # Use the adapter macro section.
69 if re.search(r"\b(headless_context|headless_device|backend_unavailable)\b", combined): # Recognize the optional EGL public types by name.
70 return GROUPS[5] # Use the EGL section.
71 async_start = source.find("// ---- async.hpp ----") # Locate the asynchronous runtime section.
72 egl_start = source.find("// ---- egl.hpp ----") # Locate the optional EGL section.
73 macros_start = source.find("// ---- macros.hpp ----") # Locate the adapter declaration section.
74 if egl_start >= 0 and position >= egl_start and (macros_start < 0 or position < macros_start): # Place declarations within the EGL source section in its own group.
75 return GROUPS[5] # Use the EGL section.
76 if async_start >= 0 and position >= async_start and (egl_start < 0 or position < egl_start): # Place declarations within the asynchronous source section in its own group.
77 return GROUPS[4] # Use the asynchronous section.
78 if re.search( # Recognize pipeline and registered factory declarations.
79 r"\b(pipeline|module|plugin|kernel_export|shared_library|load_kernel|make_kernel|register_kernel)\b", # Recognize pipeline composition and dynamic factory names.
80 combined, # Search the signature together with its explanation.
81 ): # Use composition and factory names to choose the pipeline group.
82 return GROUPS[3] # Use the pipeline section.
83 if re.search(r"\b(stage|dispatch|backend|kernel)\b", declaration): # Recognize dispatch dimensions, stages and kernel interfaces.
84 return GROUPS[2] # Use the kernel section.
85 if re.search( # Recognize resource types and transfer methods.
86 r"\b(buffer|image2d|access|upload|download|from_gl|host_dirty|gpu_dirty)\b", combined # Match the supported names in this API group.
87 ): # Group buffer and image synchronization APIs together.
88 return GROUPS[1] # Use the resource section.
89 if re.search( # Recognize basic setup, source export and error declarations.
90 r"\b(device|profile|glsl_source|glsl_prelude|read_file|error|resolver|extent|ceil_div)\b", # Recognize device setup, shader export and basic runtime names.
91 combined, # Search the signature together with its explanation.
92 ): # Group setup helpers with backend configuration.
93 return GROUPS[0] # Use the build and device section.
94 glsl_start = source.find("namespace glsl") # Locate the math namespace in the header.
95 runtime_start = source.find("namespace shdr", glsl_start + 1) # Find where runtime declarations follow the math section.
96 if ( # Use namespace position to identify remaining math declarations.
97 glsl_start >= 0 # Require that the math namespace was found.
98 and position >= glsl_start # Check that this declaration follows the math namespace opening.
99 and (runtime_start < 0 or position < runtime_start) # Keep math entries before the runtime namespace begins.
100 ): # Keep declarations inside the math namespace in the math group.
101 return GROUPS[6] # Use the math section.
102 return GROUPS[8] # Use the remaining public declarations section.
103
104
105def family_symbols(body: str) -> list[str]: # List functions covered by one math-family docstring.
106 """Collect names from public declarations and the header's math generator calls."""
107 body = re.sub(r"/\*[\s\S]*?\*/|//[^\n]*", "", body) # Remove comments before scanning math declarations.
108 names = [] # Collect the public names found in this family.
109 depth = 0 # Track nested bodies so only public names are listed.
110 in_macro = False # Track whether this line continues a macro definition.
111 for line in body.splitlines(): # Read each declaration and generator call in source order.
112 stripped = line.strip() # Inspect the current line after trimming indentation.
113 if in_macro or stripped.startswith("#define"): # Select public declarations surrounding the macro definitions.
114 in_macro = stripped.endswith("\\") # Track whether this line continues a macro definition.
115 continue # Skip this item and inspect the next one.
116 macro = re.match(r"SHDR_DETAIL_\w+\(([^,)]+)", stripped) # Recognize a generated macro or declaration name.
117 if macro and depth == 0: # Read a public symbol passed to a top-level math generator.
118 name = macro.group(1).strip() # Trim surrounding whitespace from the symbol name.
119 if re.fullmatch(r"[A-Za-z_]\w*", name): # Accept ordinary identifier names from a generator call.
120 names.append(name) # Keep this public function or operator name.
121 elif re.fullmatch(r"[+*/%&|^<>=!-]+", name): # Handle this source form.
122 names.append("operator" + name) # Recognize operator tokens supplied to the math generator.
123 elif depth == 0 and not stripped.startswith("#"): # Handle this source form.
124 candidates = re.findall(r"(?<![:\w])([A-Za-z_]\w*)\s*\(", line) # Scan ordinary top-level declarations outside preprocessor directives.
125 candidates = [ # Find possible function names and remove language keywords.
126 name # Keep this candidate function name.
127 for name in candidates # Inspect the names matched on this declaration line.
128 if name # Keep candidate public function names.
129 not in {"if", "for", "while", "switch", "static_assert", "decltype", "sizeof"} # Remove language constructs that resemble function calls.
130 ] # Remove language keywords from the list of possible function names.
131 if candidates: # Keep the first public function name on this declaration line.
132 names.append(candidates[0]) # Keep this public function or operator name.
133 depth += line.count("{") - line.count("}") # Track nested bodies so only public names are listed.
134 return list(dict.fromkeys(names)) # Return unique public names in source order.
135
136
137def documented_declarations(source: str) -> list[dict[str, str | int]]: # Collect public declarations and their source locations.
138 """Collect header docstrings, declarations, groups, and source line numbers."""
139 expression = re.compile(r"/\*\*[\s\S]*?\*/|(?:^[ \t]*///[^\n]*(?:\n|$))+", re.MULTILINE) # Match block docstrings and triple-slash comments.
140 entries = [] # Collect the documented public declarations.
141 for match in expression.finditer(source): # Visit each matched public docstring.
142 comment = clean_comment(match.group()) # Read the cleaned C++ comment text.
143 if not comment or "@internal" in comment or "\\internal" in comment: # Skip empty comments and entries explicitly marked internal.
144 continue # Continue to the next public docstring.
145 if re.fullmatch(r"(?:@|\\)[{}]\s*", comment): # Recognize structural Doxygen group markers.
146 continue # Continue to the next documented declaration.
147 family = re.search(r"(?:@|\\)name\s+([^\n]+)", comment) # Recognize a named group of math functions.
148 if family: # Treat a named Doxygen family as one math API entry.
149 closing = re.search(r"/\*\*\s*(?:@|\\)}\s*\*/", source[match.end() :]) # Locate the end of the documented math family.
150 family_end = match.end() + closing.start() if closing else match.end() # Limit the symbol scan to this family.
151 names = family_symbols(source[match.end() : family_end]) # Collect the public names found in this family.
152 declaration = family.group(1) + ("\n" + ", ".join(names) if names else "") # Read the displayed signature for this API entry.
153 entries.append( # Store the public signature, explanation, group and source line.
154 { # Store the location needed to link this entry to its header.
155 "comment": comment, # Keep the entry's explanatory text.
156 "declaration": declaration, # Keep the displayed C++ signature.
157 "group": GROUPS[6], # Choose the API section for this entry.
158 "line": source.count("\n", 0, match.start()) + 1, # Link this entry to its header source line.
159 } # Complete this family entry with its source location.
160 ) # Add the entire documented family as one API card.
161 continue # Continue after publishing this complete family entry.
162 declaration = declaration_after(source, match.end()) # Read the displayed signature for this API entry.
163 if not declaration or re.match(r"(?:#(?:endif|if|include)|namespace\b)", declaration): # Skip unattached comments and namespace or conditional directives.
164 continue # Continue to the next attached public declaration.
165 entries.append( # Store the public signature, explanation, group and source line.
166 { # Store the location needed to link this entry to its header.
167 "comment": comment, # Keep the entry's explanatory text.
168 "declaration": declaration, # Keep the displayed C++ signature.
169 "group": choose_group(declaration, comment, match.start(), source), # Choose the API section for this entry.
170 "line": source.count("\n", 0, match.start()) + 1, # Link this entry to its header source line.
171 } # Complete the ordinary declaration entry.
172 ) # Add this declaration to the exported API entries.
173 return entries # Return all collected public API entries.
174
175
176def render_comment(comment: str) -> str: # Convert the supported Doxygen tags to escaped HTML.
177 """Render a small Doxygen tag subset, escaping every source-content string."""
178 comment = re.sub(r"(?:@|\\)brief\s+", "", comment) # Remove the brief tag while retaining its explanation.
179 comment = re.sub(r"(?:@|\\)(?:ingroup|defgroup)\s+[^\n]+", "", comment) # Drop group-control tags from the displayed prose.
180 comment = re.sub(r"(?:@|\\)name\s+[^\n]+", "", comment) # Keep the family title in its signature.
181 comment = re.sub(r"(?:@|\\)[{}]", "", comment) # Remove Doxygen group-boundary markers from the prose.
182 paragraphs = [] # Collect escaped HTML paragraphs.
183 for paragraph in re.split(r"\n\s*\n", comment.strip()): # Render each comment paragraph separately.
184 escaped = html.escape(paragraph) # Escape text before adding supported formatting tags.
185 escaped = re.sub( # Turn a parameter tag into a label while retaining its explanation.
186 r"(?:@|\\)param(?:\[[^]]+\])?\s+(\w+)\s+", # Match an optional direction qualifier and the parameter name.
187 r"<strong>Parameter <code>\1</code>:</strong> ", # Show each parameter name beside its explanation.
188 escaped, # Keep the escaped explanation in the displayed paragraph.
189 ) # Finish rendering this parameter label.
190 for tags, label in ( # Handle each supported Doxygen label.
191 ("return|returns", "Returns"), # Label documented return values.
192 ("throw|throws|exception", "Throws"), # Label documented exceptions.
193 ("note", "Note"), # Label additional usage notes.
194 ("warning", "Requirement"), # Label required preconditions.
195 ): # Apply each label to the matching Doxygen tag.
196 escaped = re.sub( # Replace the current tag with a readable HTML label.
197 r"(?:@|\\)(?:" + tags + r")\s+", "<strong>" + label + ":</strong> ", escaped # Replace this documented tag and preserve surrounding text.
198 ) # Keep the rest of this escaped paragraph unchanged.
199 escaped = re.sub(r"`([^`]+)`", r"<code>\1</code>", escaped) # Render backtick-delimited text as inline code.
200 paragraphs.append("<p>" + escaped.replace("\n", "<br>") + "</p>") # Keep this escaped paragraph in its original order.
201 return "\n".join(paragraphs) # Return the joined HTML paragraphs.
202
203
204def export(header: Path, output: Path) -> int: # Build the searchable API page from the public header.
205 """Write deterministic HTML and return the number of exported declarations."""
206 source = header.read_text(encoding="utf-8") # Read the complete amalgamated header as UTF-8 text.
207 version_match = re.search(r'^\s*#define\s+SHDR_VERSION\s+"([^"]+)"', source, re.MULTILINE) # Read the release label from the actual exported header.
208 if not version_match: # Require the header to identify the documented release.
209 raise SystemExit("SHDR_VERSION is missing from " + str(header)) # Avoid publishing an unlabeled API reference.
210 version = version_match.group(1) # Keep this release label separate from all API source text.
211 entries = documented_declarations(source) # Collect the documented public declarations.
212 if not entries: # Require public API comments in the selected header.
213 raise SystemExit("No API docstrings found in " + str(header)) # Identify the header that needs public API comments.
214 navigation = [] # Collect links to the visible API sections.
215 sections = [] # Collect the rendered API groups.
216 for index, group in enumerate(GROUPS): # Render each API group in its displayed order.
217 selected = [entry for entry in entries if entry["group"] == group] # Visit groups in the order shown by the navigation.
218 if not selected: # Select groups containing public entries.
219 continue # Skip this item and inspect the next one.
220 anchor = "group-" + str(index) # Continue to the next populated group.
221 navigation.append(f'<a href="#{anchor}">{html.escape(group)}</a>') # Link the displayed section from the page navigation.
222 articles = [] # Collect each declaration card in this section.
223 for entry in selected: # Render each declaration in this group.
224 signature = str(entry["declaration"]) # Read this entry's displayed C++ declaration.
225 query = re.sub(r"\s+", " ", signature + " " + str(entry["comment"])).lower() # Normalize the entry text for case-insensitive search.
226 articles.append( # Add one escaped declaration card to this API section.
227 '<article class="entry" data-search="' + html.escape(query, quote=True) + '">\n' # Keep source text escaped in the HTML card.
228 '<pre><code class="syntax-highlight">' # Keep each highlighted declaration in its own readable code block.
229 + highlight_code(signature, "cpp") # Color the C++ declaration while preserving its exact displayed text.
230 + "</code></pre>\n" # Close the displayed signature before its explanation.
231 + render_comment(str(entry["comment"])) # Show the formatted docstring below its declaration.
232 + "\n" # Separate the rendered pieces with readable line breaks.
233 + f'<a class="source" href="#L{entry["line"]}">shdr.h, line {entry["line"]}</a>\n</article>' # Link the API card to its declaration in the source browser.
234 ) # Finish this part of the API page.
235 sections.append( # Add the rendered group to the complete page.
236 f'<section id="{anchor}"><h2>{html.escape(group)}</h2>\n' # Start the section with a stable anchor and escaped group title.
237 + "\n".join(articles) # Place this group's declaration cards below its title.
238 + "\n</section>" # Close the section after its final declaration card.
239 ) # Finish this part of the API page.
240 document = ( # Assemble the complete standalone HTML page.
241 """<!doctype html>
242<html lang="en"> <!-- Declare the language used by the documentation. -->
243<head> <!-- Keep page metadata and styles separate from its content. -->
244<meta charset="utf-8"> <!-- Display source names and math symbols as UTF-8. -->
245<meta name="viewport" content="width=device-width,initial-scale=1"> <!-- Fit small screens. -->
246<title>shdr __SHDR_DOC_VERSION__ API reference</title> <!-- Name the browser tab and release. -->
247<style> /* Keep the API page usable without a stylesheet download. */
248:root { /* Set colors for the default light theme. */
249 color-scheme: light dark; /* Allow matching browser controls in either theme. */
250 --bg: #fbfcff; /* Use a light page background. */
251 --fg: #1f2937; /* Use dark text with readable contrast. */
252 --border: #d7dfe9; /* Separate API entries with a subtle rule. */
253 --accent: #1d4ed8; /* Give links a recognizable blue color. */
254 --code: #eef2f8; /* Set code apart from prose. */
255} /* Finish the light-theme values. */
256@media (prefers-color-scheme: dark) { /* Follow the reader's system theme. */
257 :root { /* Override only the theme colors. */
258 --bg: #111827; /* Use a dark page background. */
259 --fg: #e5e7eb; /* Keep text readable on the dark background. */
260 --border: #374151; /* Keep entry rules visible without high contrast. */
261 --accent: #93c5fd; /* Lighten links for the dark background. */
262 --code: #1f2937; /* Keep code panels distinct from the page. */
263 } /* Finish the dark color overrides. */
264} /* Keep layout rules shared by both themes. */
265* { box-sizing: border-box; } /* Include padding in element widths. */
266body { /* Use a readable system font and remove default page margins. */
267 margin: 0; /* Let the main content control spacing. */
268 background: var(--bg); /* Follow the selected background color. */
269 color: var(--fg); /* Follow the selected foreground color. */
270 font: 16px/1.55 system-ui, sans-serif; /* Give prose a comfortable line height. */
271} /* Finish the page defaults. */
272main { max-width: 1000px; margin: 0 auto; padding: 32px 24px 64px; } /* Center the readable column. */
273a { color: var(--accent); } /* Make links distinct in both themes. */
274h1 { line-height: 1.1; } /* Keep the page title compact. */
275h2 { margin-top: 44px; } /* Separate each API section. */
276nav { display: flex; gap: 12px 20px; flex-wrap: wrap; margin: 24px 0; } /* Wrap section links. */
277code { font: 0.92em ui-monospace, SFMono-Regular, Consolas, monospace; } /* Align source characters. */
278pre { overflow: auto; padding: 16px; background: var(--code); border-radius: 8px; } /* Frame code blocks. */
279pre code { white-space: pre-wrap; } /* Wrap long declarations on narrow screens. */
280.entry { border-top: 1px solid var(--border); padding: 8px 0 18px; } /* Separate declaration cards. */
281.entry p { margin: 10px 0; } /* Keep explanations close to their signatures. */
282.source { font-size: 13px; } /* Keep source-line links smaller than prose. */
283input { /* Make the search control match the page. */
284 width: 100%; /* Span the documentation column. */
285 padding: 12px; /* Keep the text easy to edit. */
286 border: 1px solid var(--border); /* Show the input boundary. */
287 border-radius: 6px; /* Match the code-panel corners. */
288 background: var(--bg); /* Follow the selected theme. */
289 color: var(--fg); /* Keep the search text readable. */
290 font: inherit; /* Use the same type size as the prose. */
291} /* Finish the search control styles. */
292.muted { font-size: 14px; opacity: .78; } /* De-emphasize counts and generation notes. */
293.hidden { display: none; } /* Hide entries excluded by the search. */
294table { border-collapse: collapse; width: 100%; } /* Keep requirement columns aligned. */
295th, td { padding: 8px; border-bottom: 1px solid var(--border); text-align: left; } /* Space table cells. */
296.source-code code { white-space: pre; } /* Preserve exact header indentation. */
297.line:target { background: var(--border); } /* Highlight a linked header line. */
298.line-number { /* Keep source line numbers separate from the code. */
299 display: inline-block; /* Reserve a consistent gutter. */
300 width: 5em; /* Leave room for large header line numbers. */
301 text-align: right; /* Align numbers by their final digit. */
302 padding-right: 1.5em; /* Separate numbers from the source text. */
303 opacity: .65; /* Keep the source itself more prominent. */
304 text-decoration: none; /* Avoid underlining every line number. */
305} /* Finish the source gutter styles. */
306</style> <!-- All page styling is embedded above. -->
307<link rel="stylesheet" href="site.css"> <!-- Match the navigation and appearance of the other documentation pages. -->
308</head> <!-- The remaining page contains the API itself. -->
309<body> <!-- Display the guide and its generated declaration cards. -->
310<a class="skip-link" href="#content">Skip to content</a> <!-- Let keyboard readers skip the repeated navigation. -->
311<header class="site-header"><div class="header-inner"><a class="brand" href="index.html">shdr<span class="release-version">__SHDR_DOC_VERSION__</span><span>CPU + GPU compute</span></a><nav class="site-nav" aria-label="Main navigation"><a href="index.html">Start</a><a href="examples.html">Examples</a><a href="results.html">Results</a><a href="corpus.html">Public shaders</a><a href="api.html" aria-current="page">API</a></nav></div></header> <!-- Connect the reference to the complete documentation site. -->
312<div class="page-shell"><main class="page-main" id="content"> <!-- Keep the page within the readable content column. -->
313<p class="eyebrow">Runtime reference</p> <!-- Distinguish declarations from the beginner walkthrough. -->
314<h1>shdr __SHDR_DOC_VERSION__ API reference</h1> <!-- Name the library and release being documented. -->
315<p>A C++11 header for CPU and GPU compute pipelines. The build helper turns native GLSL into CPU adapters and embeds GPU source. Applications create kernels, bind resources and run dispatches. <a href="source/shdr.h.html">Read or download the single-file runtime header</a>.</p> <!-- Connect the API to the runtime used by an application. -->
316<p>Start with the <a href="index.html">walkthrough</a>. Read about <a href="resources.html">resources and bindings</a>, <a href="portability.html">supported shader features</a>, <a href="async.html">asynchronous execution</a>, <a href="imaging.html">imaging</a>, <a href="evt3.html">event-camera processing</a>, <a href="neural.html">neural models</a>, <a href="signals.html">signal processing</a> and <a href="datasets.html">public data</a>. The <a href="examples.html">example catalog</a> connects each complete program to its kernels.</p> <!-- Link the beginner guides. -->
317<table> <!-- Compare the backend requirements. -->
318<thead><tr><th scope="col">Backend</th><th scope="col">Minimum requirement</th></tr></thead> <!-- Label each requirement column. -->
319<tbody> <!-- List the supported execution choices. -->
320<tr><td>CPU</td><td>C++11 compiler and host resources.</td></tr> <!-- State the CPU requirement. -->
321<tr><td>OpenGL ES</td><td>ES 3.1 with GLSL ES 3.10, or ES 3.2 with GLSL ES 3.20.</td></tr> <!-- State the GLES profiles. -->
322<tr><td>Desktop OpenGL</td><td>GL 4.3 with GLSL 4.30, or a newer supported profile.</td></tr> <!-- State the desktop minimum. -->
323</tbody> <!-- Finish the backend choices. -->
324</table> <!-- Finish the requirement comparison. -->
325<p>Enable <code>SHDR_ENABLE_EGL=1</code> and link EGL to create a headless GPU device. An existing application context can supply its own function resolver. GPU calls and final resource release use the context thread. The runtime handles transfers, dispatches, fence polling and bounded CPU work queues.</p> <!-- Explain context ownership and execution. -->
326<nav class="section-nav" aria-label="API sections">"""
327 + "\n".join(navigation) # Insert the generated links to all nonempty API groups.
328 + """</nav>
329<label for="search">Find an API declaration</label><input id="search" type="search" placeholder="Try buffer, bind, glsl_source, pipeline, or imageLoad" autocomplete="off">
330<p class="muted" id="count" aria-live="polite">"""
331 + str(len(entries)) # Show the number of documented public entries.
332 + """ documented declarations.</p>
333"""
334 + "\n".join(sections) # Insert the rendered declaration sections in display order.
335 + """
336<details id="source"><summary>Browse the complete documented header source</summary><pre class="source-code"><code class="syntax-highlight">"""
337 + "\n".join( # Add the escaped header text to the source browser.
338 f'<span class="line" id="L{line}"><a class="line-number" href="#L{line}">{line}</a>{text}</span>' # Keep balanced token spans inside their numbered source line.
339 for line, text in enumerate(highlight_lines(source, "cpp")[: len(source.splitlines())], 1) # Tokenize the complete header so multiline comments retain their state.
340 ) # Finish this part of the API page.
341 + """</code></pre></details>
342<script> // Keep search and source navigation available without external scripts.
343const search = document.getElementById('search'); // Find the search input.
344const entries = Array.from(document.querySelectorAll('.entry')); // Keep all declaration cards.
345search.addEventListener('input', () => { // Refresh results after each text edit.
346 const terms = search.value.toLowerCase().trim().split(/\\s+/).filter(Boolean); // Ignore case and empty words.
347 let count = 0; // Count the entries that match every search word.
348 entries.forEach(entry => { // Inspect each declaration's searchable text.
349 const visible = terms.every(term => entry.dataset.search.includes(term)); // Require all words to match.
350 entry.classList.toggle('hidden', !visible); // Hide unmatched declaration cards.
351 if (visible) count++; // Count visible cards for the status text.
352 }); // Every declaration now has its current visibility.
353 document.querySelectorAll('section').forEach(section => { // Hide groups with no visible declarations.
354 const visible = section.querySelector('.entry:not(.hidden)'); // Find any visible card in this group.
355 section.classList.toggle('hidden', !visible); // Keep only groups with results.
356 }); // Group headings now match their visible cards.
357 document.getElementById('count').textContent = count + ' documented declarations match.'; // Show the result count.
358}); // The input listener handles each new query.
359function showSource() { // Open the header when a source-line link is selected.
360 if (/^#L[0-9]+$/.test(location.hash)) { // Recognize only generated line anchors.
361 document.getElementById('source').open = true; // Expand the source browser.
362 const line = document.getElementById(location.hash.slice(1)); // Locate the linked line.
363 if (line) line.scrollIntoView(); // Move the selected line into view.
364 } // Ordinary section links leave the source browser unchanged.
365} // Source navigation is also used for direct page links.
366document.addEventListener('click', event => { // Open the source before scrolling to its anchor.
367 if (event.target.closest('a[href^="#L"]')) { // Recognize a source-line link.
368 document.getElementById('source').open = true; // Reveal the line's enclosing section.
369 } // Other page links keep their usual behavior.
370}); // Clicking a line link can now scroll to visible content.
371window.addEventListener('hashchange', showSource); // Handle forward and back navigation.
372showSource(); // Honor a source-line anchor in the initial page URL.
373</script> <!-- Finish the embedded page behavior. -->
374</main></div> <!-- Finish the documentation column. -->
375<footer class="footer"><p>shdr __SHDR_DOC_VERSION__ · C++11 compute pipelines · <a href="portability.html">Portability and requirements</a> · <a href="source/index.html">Read the source</a></p></footer> <!-- Keep the next useful links available after the reference. -->
376</body> <!-- Finish the visible page. -->
377</html> <!-- The reference needs no network resources. -->
378"""
379 ) # Finish this part of the API page.
380 document = document.replace("__SHDR_DOC_VERSION__", html.escape(version)) # Apply the header's release label to page chrome only.
381 document = add_stylesheet(document, "highlight.css") # Load the local stylesheet for precomputed syntax colors.
382 output.parent.mkdir(parents=True, exist_ok=True) # Create the output directory before writing the page.
383 output.write_text(document, encoding="utf-8") # Write the completed standalone API page.
384 return len(entries) # Report the number of exported entries.
385
386
387def main() -> None: # Read options and run this command-line tool.
388 """Parse optional paths and export the library's API docstrings."""
389 parser = argparse.ArgumentParser(description=__doc__) # Describe the command and its supported options.
390 parser.add_argument("--header", type=Path, default=ROOT / "shdr.h") # Allow an alternate header while defaulting to this checkout's shdr.h.
391 parser.add_argument("--output", type=Path, default=ROOT / "docs" / "api.html") # Allow an alternate output while defaulting to docs/api.html.
392 args = parser.parse_args() # Read the header and output paths selected by the user.
393 print(f"Exported {export(args.header, args.output)} documented declarations to {args.output}") # Print the entry count and destination of the completed export.
394
395
396if __name__ == "__main__": # Run only when this file is invoked as a command.
397 main() # Read options and run the requested checks.