Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ tests/fixtures/vulkan_samples
.claude/*
!.claude/skills/
_site/
docs-astro/.astro/
docs-astro/dist/
docs-astro/node_modules/
61 changes: 54 additions & 7 deletions src/rdc/handlers/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,58 @@ def get_default_disasm_target(controller: Any) -> str:
return str(targets[0]) if targets else "SPIR-V"


def _shader_value_lane_name(var_type: Any) -> str:
"""Return the ShaderValue lane name for a reflected variable type."""
if isinstance(var_type, int):
return {
0: "f32v",
1: "f64v",
2: "f16v",
3: "s32v",
4: "u32v",
5: "s16v",
6: "u16v",
7: "s64v",
8: "u64v",
9: "s8v",
10: "u8v",
11: "u32v",
}.get(var_type, "f32v")

type_name = getattr(var_type, "name", var_type)
type_str = str(type_name).lower()
if "double" in type_str or type_str in {"f64", "float64"}:
return "f64v"
if "half" in type_str or type_str in {"f16", "float16"}:
return "f16v"
if "uint64" in type_str or "ulong" in type_str or type_str == "u64":
return "u64v"
if "uint16" in type_str or "ushort" in type_str or type_str == "u16":
return "u16v"
if "uint8" in type_str or "ubyte" in type_str or type_str == "u8":
return "u8v"
if "uint" in type_str or type_str in {"u32", "uint32"}:
return "u32v"
if "int64" in type_str or "slong" in type_str or type_str in {"s64", "long"}:
return "s64v"
if "int16" in type_str or "sshort" in type_str or type_str in {"s16", "short"}:
return "s16v"
if "int8" in type_str or "sbyte" in type_str or type_str == "s8":
return "s8v"
if "sint" in type_str or "int" in type_str or type_str in {"s32", "int32"}:
return "s32v"
if "bool" in type_str:
return "u32v"
return "f32v"


Comment thread
coderabbitai[bot] marked this conversation as resolved.
def _shader_value_lane_fallback(lane_name: str) -> list[float | int]:
"""Return a type-stable fallback for a missing ShaderValue lane."""
if lane_name.startswith(("u", "s")):
return [0] * 16
return [0.0] * 16


def _flatten_shader_var(var: Any) -> dict[str, Any]:
"""Recursively convert a ShaderVariable to a dict."""
members = getattr(var, "members", [])
Expand All @@ -529,13 +581,8 @@ def _flatten_shader_var(var: Any) -> dict[str, Any]:
if val is None:
values: list[Any] = []
else:
type_str = str(getattr(var, "type", "")).lower()
if "uint" in type_str:
values = list(getattr(val, "u32v", [0.0] * 16)[:count])
elif "int" in type_str or "sint" in type_str:
values = list(getattr(val, "s32v", [0] * 16)[:count])
else:
values = list(getattr(val, "f32v", [0.0] * 16)[:count])
lane_name = _shader_value_lane_name(getattr(var, "type", ""))
values = list(getattr(val, lane_name, _shader_value_lane_fallback(lane_name))[:count])

return {
"name": var.name,
Expand Down
22 changes: 5 additions & 17 deletions src/rdc/handlers/buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
_error_response,
_result_response,
_set_frame_event,
_shader_value_lane_fallback,
_shader_value_lane_name,
get_pipeline_for_stage,
require_pipe,
)
Expand Down Expand Up @@ -46,20 +48,7 @@ def _decode_index_buffer(data: bytes, stride: int) -> list[int]:

def _shader_variable_value_kind(var_type: Any) -> str:
"""Return the ShaderValue lane name for a reflected variable type."""
if isinstance(var_type, int):
if var_type == 4:
return "u32v"
if var_type == 5:
return "s32v"
return "f32v"

type_name = getattr(var_type, "name", var_type)
type_str = str(type_name).lower()
if "uint" in type_str or type_str in {"u32", "uint32"}:
return "u32v"
if "sint" in type_str or "int" in type_str or type_str in {"s32", "int32"}:
return "s32v"
return "f32v"
return _shader_value_lane_name(var_type)


def _shader_variable_values(var: Any) -> Any:
Expand All @@ -69,9 +58,8 @@ def _shader_variable_values(var: Any) -> Any:
return None
r = getattr(var, "rows", 1) or 1
c = getattr(var, "columns", 1) or 1
lane = getattr(val, _shader_variable_value_kind(getattr(var, "type", "")), None)
if lane is None:
return val
lane_name = _shader_variable_value_kind(getattr(var, "type", ""))
lane = getattr(val, lane_name, _shader_value_lane_fallback(lane_name))
values = [lane[ri * c + ci] for ri in range(r) for ci in range(c)]
return values if len(values) > 1 else values[0]

Expand Down
18 changes: 9 additions & 9 deletions src/rdc/handlers/debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
_get_flat_actions,
_result_response,
_set_frame_event,
_shader_value_lane_fallback,
_shader_value_lane_name,
)
from rdc.handlers._types import Handler

Expand All @@ -29,21 +31,19 @@ def _format_var_value(var: Any) -> list[float | int]:
val = var.value
if val is None:
return [0.0] * count
var_type = str(getattr(var, "type", "float")).lower()
if "uint" in var_type or "u32" in var_type:
return list(val.u32v[:count])
if "int" in var_type or "s32" in var_type or "sint" in var_type:
return list(val.s32v[:count])
return list(val.f32v[:count])
lane_name = _shader_value_lane_name(getattr(var, "type", "float"))
return list(getattr(val, lane_name, _shader_value_lane_fallback(lane_name))[:count])


def _format_var_type(var: Any) -> str:
"""Return a human-readable type string for a ShaderVariable."""
t = str(getattr(var, "type", "float")).lower()
if "uint" in t or "u32" in t:
lane_name = _shader_value_lane_name(getattr(var, "type", "float"))
if lane_name.startswith("u"):
return "uint"
if "int" in t or "s32" in t or "sint" in t:
if lane_name.startswith("s"):
return "int"
if lane_name == "f64v":
return "double"
return "float"


Expand Down
10 changes: 9 additions & 1 deletion tests/mocks/mock_renderdoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1000,11 +1000,19 @@ class MeshFormat:

@dataclass
class ShaderValue:
"""Mock for ShaderValue union (real API has f32v, u32v, s32v, f64v)."""
"""Mock for ShaderValue union lanes used by RenderDoc."""

f16v: list[float] = field(default_factory=lambda: [0.0] * 16)
f32v: list[float] = field(default_factory=lambda: [0.0] * 16)
f64v: list[float] = field(default_factory=lambda: [0.0] * 16)
u8v: list[int] = field(default_factory=lambda: [0] * 16)
s8v: list[int] = field(default_factory=lambda: [0] * 16)
u16v: list[int] = field(default_factory=lambda: [0] * 16)
s16v: list[int] = field(default_factory=lambda: [0] * 16)
u32v: list[int] = field(default_factory=lambda: [0] * 16)
s32v: list[int] = field(default_factory=lambda: [0] * 16)
u64v: list[int] = field(default_factory=lambda: [0] * 16)
s64v: list[int] = field(default_factory=lambda: [0] * 16)


@dataclass
Expand Down
108 changes: 108 additions & 0 deletions tests/unit/test_buffer_decode.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,114 @@ def test_uint_numeric_type_extracts_u32v(self, state: DaemonState) -> None:
assert r["variables"][0]["type"] == "4"
assert r["variables"][0]["value"] == [13, 17, 19, 23]

def test_uint_missing_lane_uses_int_fallback(self, state: DaemonState) -> None:
"""Missing integer lanes return integer zeros, not raw value objects."""
state.adapter.controller.GetCBufferVariableContents = lambda *args: [
ShaderVariable(
name="missingUint",
type=4,
rows=1,
columns=2,
value=SimpleNamespace(),
)
]
resp, _ = _handle_request(
rpc_request(
"cbuffer_decode", {"eid": 10, "set": 0, "binding": 0}, token="abcdef1234567890"
),
state,
)
value = resp["result"]["variables"][0]["value"]
assert value == [0, 0]
assert all(type(item) is int for item in value)

def test_64_bit_integer_numeric_types_use_64_bit_lanes(self, state: DaemonState) -> None:
"""RenderDoc exposes signed/unsigned 64-bit ints as numeric types 7 and 8."""
state.adapter.controller.GetCBufferVariableContents = lambda *args: [
ShaderVariable(
name="signedLong",
type=7,
rows=1,
columns=1,
value=ShaderValue(s64v=[-9_000_000_000] + [0] * 15),
),
ShaderVariable(
name="unsignedLong",
type=8,
rows=1,
columns=1,
value=ShaderValue(u64v=[18_000_000_000] + [0] * 15),
),
]
resp, _ = _handle_request(
rpc_request(
"cbuffer_decode", {"eid": 10, "set": 0, "binding": 0}, token="abcdef1234567890"
),
state,
)
variables = resp["result"]["variables"]
assert variables[0]["value"] == -9_000_000_000
assert variables[1]["value"] == 18_000_000_000

@pytest.mark.parametrize(
("var_type", "lane_name"),
[
(9, "s8v"),
(10, "u8v"),
("sbyte", "s8v"),
("ubyte", "u8v"),
],
)
def test_byte_integer_types_use_byte_lanes(self, var_type: object, lane_name: str) -> None:
"""Byte-sized reflected variables map to byte ShaderValue lanes."""
from rdc.handlers._helpers import _shader_value_lane_name

assert _shader_value_lane_name(var_type) == lane_name

def test_double_numeric_type_extracts_f64v(self, state: DaemonState) -> None:
"""RenderDoc exposes double cbuffer variables as numeric type 1."""
double_val = ShaderValue(f32v=[0.0] * 16, f64v=[1.25, 2.5] + [0.0] * 14)
state.adapter.controller.GetCBufferVariableContents = lambda *args: [
ShaderVariable(
name="clipRange",
type=1,
rows=1,
columns=2,
value=double_val,
)
]
resp, _ = _handle_request(
rpc_request(
"cbuffer_decode", {"eid": 10, "set": 0, "binding": 0}, token="abcdef1234567890"
),
state,
)
r = resp["result"]
assert r["variables"][0]["type"] == "1"
assert r["variables"][0]["value"] == [1.25, 2.5]

def test_double_named_type_extracts_f64v(self, state: DaemonState) -> None:
"""Mocks and adapters may expose reflected double types by name."""
double_val = ShaderValue(f32v=[0.0] * 16, f64v=[3.5] + [0.0] * 15)
state.adapter.controller.GetCBufferVariableContents = lambda *args: [
ShaderVariable(
name="exposure",
type="double",
rows=1,
columns=1,
value=double_val,
)
]
resp, _ = _handle_request(
rpc_request(
"cbuffer_decode", {"eid": 10, "set": 0, "binding": 0}, token="abcdef1234567890"
),
state,
)
r = resp["result"]
assert r["variables"][0]["type"] == "double"
assert r["variables"][0]["value"] == 3.5


class TestCbufferRaw:
def test_stageful_raw_data_node_visible_to_vfs_ls(self, state: DaemonState) -> None:
Expand Down
57 changes: 57 additions & 0 deletions tests/unit/test_daemon_shader_api_fix.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from __future__ import annotations

from types import SimpleNamespace

import mock_renderdoc as rd
from conftest import make_daemon_state, rpc_request

Expand Down Expand Up @@ -236,6 +238,61 @@ def test_shader_constants_returns_structured_variables() -> None:
assert "data" not in r["constants"][0]


def test_shader_constants_double_variable_uses_f64v() -> None:
"""Double ShaderVariable values are flattened from f64v."""
ctrl = rd.MockReplayController()
ps_id = rd.ResourceId(101)
ctrl._pipe_state._shaders[rd.ShaderStage.Pixel] = ps_id
ctrl._pipe_state._entry_points[rd.ShaderStage.Pixel] = "main_ps"
ctrl._pipe_state._reflections[rd.ShaderStage.Pixel] = rd.ShaderReflection(
resourceId=ps_id,
entryPoint="main_ps",
constantBlocks=[rd.ConstantBlock(name="Globals", fixedBindNumber=0)],
)
ctrl._pipe_state._cbuffer_descriptors[(rd.ShaderStage.Pixel, 0)] = rd.Descriptor(
resource=rd.ResourceId(500),
)
ctrl._cbuffer_variables[(rd.ShaderStage.Pixel, 0)] = [
rd.ShaderVariable(
name="g_Exposure",
type=1,
rows=1,
columns=1,
value=rd.ShaderValue(f32v=[0.0] * 16, f64v=[4.25] + [0.0] * 15),
),
]
ctrl._actions = [rd.ActionDescription(eventId=10, flags=rd.ActionFlags.Drawcall)]

state = DaemonState(capture="x.rdc", current_eid=0, token="tok")
state.adapter = RenderDocAdapter(controller=ctrl, version=(1, 41))
state.max_eid = 100

resp, _ = _handle_request(rpc_request("shader_constants", {"eid": 10, "stage": "ps"}), state)
r = resp["result"]
v = r["constants"][0]["variables"][0]
assert v["name"] == "g_Exposure"
assert v["type"] == "1"
assert v["value"] == [4.25]


def test_flatten_shader_var_integer_missing_lane_uses_int_fallback() -> None:
"""Missing integer lanes keep integer zero values in flattened output."""
from rdc.handlers._helpers import _flatten_shader_var

var = SimpleNamespace(
name="missing_uint",
type=4,
rows=1,
columns=2,
value=SimpleNamespace(),
)

result = _flatten_shader_var(var)

assert result["value"] == [0, 0]
assert all(type(value) is int for value in result["value"])


def test_shader_constants_struct_variable_recurses() -> None:
"""Struct ShaderVariable with members recurses into children."""
ctrl = rd.MockReplayController()
Expand Down
Loading
Loading