Files
XCEngine/engine/tools/renderdoc_parser/tools/shader_tools.py
ssdfasd fb01beb959 Add renderdoc_parser: direct-call Python interface for RenderDoc capture analysis
- Convert from MCP protocol layer to direct Python function calls
- 42 functions across 9 modules: session, event, pipeline, resource, data, shader, advanced, performance, diagnostic
- Requires Python 3.6 (renderdoc.pyd is compiled for Python 3.6)
- Fix renderdoc API calls: GetColorBlends, GetStencilFaces, GetViewport(i), GetScissor(i)
- Remove Python 3.10+ type annotations for Python 3.6 compatibility
- Add README.md with full API documentation
- Includes test.py for basic smoke testing
2026-03-23 18:46:20 +08:00

334 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Shader analysis tools: disassemble_shader, get_shader_reflection, get_cbuffer_contents."""
from typing import Optional
from ..session import get_session
from ..util import (
rd,
make_error,
SHADER_STAGE_MAP,
serialize_shader_variable,
serialize_sig_element,
)
def _reflection_fallback(stage: str, refl) -> dict:
result: dict = {
"stage": stage,
"source_type": "reflection_only",
"resource_id": str(refl.resourceId),
"entry_point": refl.entryPoint,
"input_signature": [serialize_sig_element(s) for s in refl.inputSignature],
"output_signature": [serialize_sig_element(s) for s in refl.outputSignature],
"constant_blocks": [
{"name": cb.name, "byte_size": cb.byteSize} for cb in refl.constantBlocks
],
"read_only_resources": [
{"name": ro.name, "type": str(ro.textureType)}
for ro in refl.readOnlyResources
],
"read_write_resources": [
{"name": rw.name, "type": str(rw.textureType)}
for rw in refl.readWriteResources
],
"note": "Disassembly unavailable — showing reflection data only",
}
return result
def disassemble_shader(
stage: str,
target: Optional[str] = None,
event_id: Optional[int] = None,
line_range: Optional[list] = None,
search: Optional[str] = None,
) -> dict:
"""Disassemble the shader bound at the specified stage.
If target is omitted, tries all available targets in order and returns the
first successful result. Falls back to reflection info if all disassembly fails.
Args:
stage: Shader stage (vertex, hull, domain, geometry, pixel, compute).
target: Disassembly target/format. If omitted, tries all available targets.
event_id: Optional event ID to navigate to first.
line_range: [start_line, end_line] (1-based). Only return lines in this range.
search: Keyword to search in the disassembly. Returns matching lines with
5 lines of context before and after each match.
"""
session = get_session()
err = session.require_open()
if err:
return err
err = session.ensure_event(event_id)
if err:
return err
stage_enum = SHADER_STAGE_MAP.get(stage.lower())
if stage_enum is None:
return make_error(f"Unknown shader stage: {stage}", "API_ERROR")
state = session.controller.GetPipelineState()
refl = state.GetShaderReflection(stage_enum)
if refl is None:
return make_error(f"No shader bound at stage '{stage}'", "API_ERROR")
if stage_enum == rd.ShaderStage.Compute:
pipe = state.GetComputePipelineObject()
else:
pipe = state.GetGraphicsPipelineObject()
targets = session.controller.GetDisassemblyTargets(True)
if not targets:
return _reflection_fallback(stage, refl)
disasm: Optional[str] = None
used_target: Optional[str] = None
if target is not None:
if target not in targets:
return make_error(
f"Unknown disassembly target: {target}. Available: {targets}",
"API_ERROR",
)
disasm = session.controller.DisassembleShader(pipe, refl, target)
used_target = target
else:
for t in targets:
try:
d = session.controller.DisassembleShader(pipe, refl, t)
if d and d.strip():
disasm = d
used_target = t
break
except Exception:
continue
if not disasm:
result = _reflection_fallback(stage, refl)
result["available_targets"] = list(targets)
return result
all_lines = disasm.splitlines()
total_lines = len(all_lines)
if line_range is not None and len(line_range) == 2:
start = max(1, line_range[0]) - 1
end = min(total_lines, line_range[1])
filtered_lines = all_lines[start:end]
disasm_out = "\n".join(filtered_lines)
note = f"Showing lines {start + 1}{end} of {total_lines}"
elif search is not None:
kw = search.lower()
context = 5
include: set[int] = set()
for i, line in enumerate(all_lines):
if kw in line.lower():
for j in range(max(0, i - context), min(total_lines, i + context + 1)):
include.add(j)
if not include:
note = f"No matches for '{search}' in {total_lines} lines"
disasm_out = ""
else:
result_lines = []
prev = -2
for i in sorted(include):
if i > prev + 1:
result_lines.append("...")
result_lines.append(f"{i + 1:4d}: {all_lines[i]}")
prev = i
disasm_out = "\n".join(result_lines)
note = f"Found '{search}' in {total_lines} total lines"
else:
disasm_out = disasm
note = None
result: dict = {
"stage": stage,
"target": used_target,
"available_targets": list(targets),
"source_type": "disasm",
"total_lines": total_lines,
"disassembly": disasm_out,
}
if note:
result["note"] = note
return result
def get_shader_reflection(
stage: str,
event_id: Optional[int] = None,
) -> dict:
"""Get reflection information for the shader at the specified stage.
Returns input/output signatures, constant buffer layouts, and resource bindings.
Args:
stage: Shader stage (vertex, hull, domain, geometry, pixel, compute).
event_id: Optional event ID to navigate to first.
"""
session = get_session()
err = session.require_open()
if err:
return err
err = session.ensure_event(event_id)
if err:
return err
stage_enum = SHADER_STAGE_MAP.get(stage.lower())
if stage_enum is None:
return make_error(f"Unknown shader stage: {stage}", "API_ERROR")
state = session.controller.GetPipelineState()
refl = state.GetShaderReflection(stage_enum)
if refl is None:
return make_error(f"No shader bound at stage '{stage}'", "API_ERROR")
result: dict = {
"stage": stage,
"resource_id": str(refl.resourceId),
"entry_point": refl.entryPoint,
}
result["input_signature"] = [serialize_sig_element(s) for s in refl.inputSignature]
result["output_signature"] = [
serialize_sig_element(s) for s in refl.outputSignature
]
cbs = []
for cb in refl.constantBlocks:
cbs.append(
{
"name": cb.name,
"byte_size": cb.byteSize,
"bind_point": cb.fixedBindNumber,
"variables_count": len(cb.variables),
}
)
result["constant_blocks"] = cbs
ros = []
for ro in refl.readOnlyResources:
ros.append(
{
"name": ro.name,
"type": str(ro.textureType),
"bind_point": ro.fixedBindNumber,
}
)
result["read_only_resources"] = ros
rws = []
for rw in refl.readWriteResources:
rws.append(
{
"name": rw.name,
"type": str(rw.textureType),
"bind_point": rw.fixedBindNumber,
}
)
result["read_write_resources"] = rws
samplers = []
for s in refl.samplers:
samplers.append(
{
"name": s.name,
"bind_point": s.fixedBindNumber,
}
)
result["samplers"] = samplers
return result
def get_cbuffer_contents(
stage: str,
cbuffer_index: int,
event_id: Optional[int] = None,
filter: Optional[str] = None,
) -> dict:
"""Get the actual values of a constant buffer at the specified shader stage.
Returns a tree of variable names and their current values.
Args:
stage: Shader stage (vertex, hull, domain, geometry, pixel, compute).
cbuffer_index: Index of the constant buffer (from get_shader_reflection).
event_id: Optional event ID to navigate to first.
filter: Case-insensitive substring to match variable names
(e.g. "ibl", "exposure", "taa", "reflection"). Only matching
variables are returned.
"""
session = get_session()
err = session.require_open()
if err:
return err
err = session.ensure_event(event_id)
if err:
return err
stage_enum = SHADER_STAGE_MAP.get(stage.lower())
if stage_enum is None:
return make_error(f"Unknown shader stage: {stage}", "API_ERROR")
state = session.controller.GetPipelineState()
refl = state.GetShaderReflection(stage_enum)
if refl is None:
return make_error(f"No shader bound at stage '{stage}'", "API_ERROR")
num_cbs = len(refl.constantBlocks)
if num_cbs == 0:
return make_error(
f"Shader at stage '{stage}' has no constant buffers",
"API_ERROR",
)
if cbuffer_index < 0 or cbuffer_index >= num_cbs:
return make_error(
f"cbuffer_index {cbuffer_index} out of range (0-{num_cbs - 1})",
"API_ERROR",
)
if stage_enum == rd.ShaderStage.Compute:
pipe = state.GetComputePipelineObject()
else:
pipe = state.GetGraphicsPipelineObject()
entry = state.GetShaderEntryPoint(stage_enum)
cb_bind = state.GetConstantBlock(stage_enum, cbuffer_index, 0)
cbuffer_vars = session.controller.GetCBufferVariableContents(
pipe,
refl.resourceId,
stage_enum,
entry,
cbuffer_index,
cb_bind.descriptor.resource,
0,
0,
)
variables = [serialize_shader_variable(v) for v in cbuffer_vars]
if filter:
kw = filter.lower()
def _var_matches(var: dict) -> bool:
if kw in var.get("name", "").lower():
return True
for m in var.get("members", []):
if _var_matches(m):
return True
return False
variables = [v for v in variables if _var_matches(v)]
return {
"stage": stage,
"cbuffer_index": cbuffer_index,
"cbuffer_name": refl.constantBlocks[cbuffer_index].name,
"filter": filter,
"variables": variables,
"variable_count": len(variables),
}