diff --git a/src/rdc/commands/descriptors.py b/src/rdc/commands/descriptors.py index 769af9f..c4b9af3 100644 --- a/src/rdc/commands/descriptors.py +++ b/src/rdc/commands/descriptors.py @@ -68,6 +68,7 @@ def _table() -> None: r.get("format", "-"), r.get("width", "-"), r.get("height", "-"), + r.get("depth", "-"), ] for r in rows ] @@ -85,6 +86,7 @@ def _table() -> None: "FORMAT", "WIDTH", "HEIGHT", + "DEPTH", ], no_header=no_header, ) diff --git a/src/rdc/commands/resources.py b/src/rdc/commands/resources.py index 9e4ea04..21bc198 100644 --- a/src/rdc/commands/resources.py +++ b/src/rdc/commands/resources.py @@ -139,12 +139,13 @@ def _table() -> None: r.get("name", "-"), r.get("width", "-"), r.get("height", "-"), + r.get("depth", "-"), r.get("format", "-"), r.get("size", "-"), ] for r in rows ] - header = ["ID", "TYPE", "NAME", "WIDTH", "HEIGHT", "FORMAT", "SIZE"] + header = ["ID", "TYPE", "NAME", "WIDTH", "HEIGHT", "DEPTH", "FORMAT", "SIZE"] write_tsv(tsv_rows, header=header, no_header=no_header) render_list( diff --git a/src/rdc/handlers/descriptor.py b/src/rdc/handlers/descriptor.py index 63df598..9e9a214 100644 --- a/src/rdc/handlers/descriptor.py +++ b/src/rdc/handlers/descriptor.py @@ -127,6 +127,9 @@ def _handle_descriptors( if tex is not None: d_row["width"] = tex.width d_row["height"] = tex.height + d_row["depth"] = getattr(tex, "depth", 1) + d_row["dimension"] = getattr(tex, "dimension", 0) + d_row["texture_type"] = _enum_name(getattr(tex, "type", "")) if type_name in ("Sampler", "ImageSampler"): s = getattr(ud, "sampler", None) if s is not None: diff --git a/src/rdc/handlers/query.py b/src/rdc/handlers/query.py index fa7e676..3d522f1 100644 --- a/src/rdc/handlers/query.py +++ b/src/rdc/handlers/query.py @@ -166,6 +166,9 @@ def enrich_resource_row(row: dict[str, Any], state: DaemonState) -> dict[str, An fmt = getattr(tex, "format", None) row["width"] = tex.width row["height"] = tex.height + row["depth"] = getattr(tex, "depth", 1) + row["dimension"] = getattr(tex, "dimension", 0) + row["texture_type"] = _enum_name(getattr(tex, "type", "")) row["format"] = fmt.Name() if fmt is not None and hasattr(fmt, "Name") else "" row["size"] = getattr(tex, "byteSize", 0) return row diff --git a/src/rdc/handlers/texture.py b/src/rdc/handlers/texture.py index 628e6a8..809e359 100644 --- a/src/rdc/handlers/texture.py +++ b/src/rdc/handlers/texture.py @@ -332,9 +332,12 @@ def _handle_tex_stats( return _error_response( request_id, -32001, f"mip {mip} out of range (max: {tex.mips - 1})" ), True - if array_slice < 0 or array_slice >= tex.arraysize: + slice_count = tex.arraysize + if tex.type == rd.TextureType.Texture3D: + slice_count = max(1, tex.depth >> mip) + if array_slice < 0 or array_slice >= slice_count: return _error_response( - request_id, -32001, f"slice {array_slice} out of range (max: {tex.arraysize - 1})" + request_id, -32001, f"slice {array_slice} out of range (max: {slice_count - 1})" ), True sub = rd.Subresource() diff --git a/tests/unit/test_descriptors_commands.py b/tests/unit/test_descriptors_commands.py index 1fbcc50..f3d9ab3 100644 --- a/tests/unit/test_descriptors_commands.py +++ b/tests/unit/test_descriptors_commands.py @@ -25,6 +25,9 @@ def _result() -> dict[str, Any]: "format": "BC1_SRGB", "width": 512, "height": 512, + "depth": 4, + "dimension": 3, + "texture_type": "Texture3D", } ], } @@ -38,6 +41,8 @@ def test_descriptors_table(monkeypatch) -> None: assert "g_textures" in res.output assert "BC1_SRGB" in res.output assert "512" in res.output + assert "DEPTH" in res.output + assert "\t4\n" in res.output def test_descriptors_binding_filter_forwarded(monkeypatch) -> None: diff --git a/tests/unit/test_descriptors_daemon.py b/tests/unit/test_descriptors_daemon.py index e90a411..5c53602 100644 --- a/tests/unit/test_descriptors_daemon.py +++ b/tests/unit/test_descriptors_daemon.py @@ -223,7 +223,13 @@ def test_descriptors_binding_name_correlation() -> None: state.res_names = {371: "2D Image 371"} state.tex_map = { 371: SimpleNamespace( - width=512, height=512, format=SimpleNamespace(Name=lambda: "BC1_SRGB"), byteSize=174776 + width=64, + height=64, + depth=4, + dimension=3, + type=rd.TextureType.Texture3D, + format=SimpleNamespace(Name=lambda: "BC1_SRGB"), + byteSize=174776, ) } d = _call(state, "descriptors", eid=16)["result"]["descriptors"][0] @@ -232,8 +238,11 @@ def test_descriptors_binding_name_correlation() -> None: assert d["array_element"] == 46 assert d["resource_id"] == 371 assert d["resource_name"] == "2D Image 371" - assert d["width"] == 512 - assert d["height"] == 512 + assert d["width"] == 64 + assert d["height"] == 64 + assert d["depth"] == 4 + assert d["dimension"] == 3 + assert d["texture_type"] == "Texture3D" def test_descriptors_binding_empty_without_reflection() -> None: diff --git a/tests/unit/test_formatters.py b/tests/unit/test_formatters.py index 4bb8b1f..9e86e58 100644 --- a/tests/unit/test_formatters.py +++ b/tests/unit/test_formatters.py @@ -303,9 +303,9 @@ def test_resources_table(self, monkeypatch) -> None: result = CliRunner().invoke(main, ["resources"]) assert result.exit_code == 0 assert result.output == ( - "ID\tTYPE\tNAME\tWIDTH\tHEIGHT\tFORMAT\tSIZE\n" - "10\tTexture\talbedo\t-\t-\t-\t-\n" - "20\tBuffer\tverts\t-\t-\t-\t-\n" + "ID\tTYPE\tNAME\tWIDTH\tHEIGHT\tDEPTH\tFORMAT\tSIZE\n" + "10\tTexture\talbedo\t-\t-\t-\t-\t-\n" + "20\tBuffer\tverts\t-\t-\t-\t-\t-\n" ) def test_counters_quiet(self, monkeypatch) -> None: diff --git a/tests/unit/test_resources_commands.py b/tests/unit/test_resources_commands.py index cb77cf9..ba5b334 100644 --- a/tests/unit/test_resources_commands.py +++ b/tests/unit/test_resources_commands.py @@ -35,6 +35,9 @@ def test_resources_enriched_columns(monkeypatch) -> None: "name": "2D Image 371", "width": 512, "height": 512, + "depth": 4, + "dimension": 3, + "texture_type": "Texture3D", "format": "BC1_SRGB", "size": 174776, } @@ -43,7 +46,8 @@ def test_resources_enriched_columns(monkeypatch) -> None: ) result = CliRunner().invoke(resources_cmd, []) assert result.exit_code == 0 - assert "ID\tTYPE\tNAME\tWIDTH\tHEIGHT\tFORMAT\tSIZE" in result.output + assert "ID\tTYPE\tNAME\tWIDTH\tHEIGHT\tDEPTH\tFORMAT\tSIZE" in result.output + assert "\t4\tBC1_SRGB\t" in result.output assert "512" in result.output assert "BC1_SRGB" in result.output diff --git a/tests/unit/test_resources_filter.py b/tests/unit/test_resources_filter.py index ded848e..2fd7fcd 100644 --- a/tests/unit/test_resources_filter.py +++ b/tests/unit/test_resources_filter.py @@ -377,6 +377,24 @@ def test_resources_list_enriched_with_dims(self) -> None: assert row["format"] == "BC1_SRGB" assert row["size"] == 174776 + def test_texture3d_metadata_is_exposed(self) -> None: + state = _state_with_resources([_make_res(8413, "RainDropSplash01.dds", "Texture")]) + state.tex_map = { + 8413: rd.TextureDescription( + resourceId=rd.ResourceId(8413), + type=rd.TextureType.Texture3D, + dimension=3, + width=64, + height=64, + depth=4, + ) + } + resp, _ = _handle_request(rpc_request("resource", {"id": 8413}), state) + resource = resp["result"]["resource"] + assert resource["depth"] == 4 + assert resource["dimension"] == 3 + assert resource["texture_type"] == "Texture3D" + def test_buffer_resource_gets_size(self) -> None: state = _state_with_resources([_make_res(99, "vtx buffer", "Buffer")]) state.buf_map = {99: SimpleNamespace(length=4096)} diff --git a/tests/unit/test_tex_stats_handler.py b/tests/unit/test_tex_stats_handler.py index 96f88ea..a77b90f 100644 --- a/tests/unit/test_tex_stats_handler.py +++ b/tests/unit/test_tex_stats_handler.py @@ -268,6 +268,52 @@ def test_tex_stats_valid_mip0_slice0() -> None: assert resp["result"]["slice"] == 0 +def _make_texture3d_state() -> DaemonState: + ctrl = rd.MockReplayController() + tex = rd.TextureDescription( + resourceId=rd.ResourceId(42), + type=rd.TextureType.Texture3D, + dimension=3, + width=64, + height=64, + depth=4, + mips=7, + arraysize=1, + ) + ctrl._textures = [tex] + ctrl._actions = [ + rd.ActionDescription(eventId=100, flags=rd.ActionFlags.Drawcall, _name="vkCmdDraw"), + ] + return make_daemon_state( + ctrl=ctrl, + current_eid=100, + rd=rd, + tex_map={42: tex}, + ) + + +def test_tex_stats_texture3d_accepts_depth_slices_for_requested_mip() -> None: + state = _make_texture3d_state() + + for mip, array_slice in ((0, 3), (1, 1), (2, 0)): + resp, _ = _handle_request( + rpc_request("tex_stats", {"id": 42, "mip": mip, "slice": array_slice}), state + ) + assert resp["result"]["mip"] == mip + assert resp["result"]["slice"] == array_slice + + +def test_tex_stats_texture3d_rejects_slice_after_mip_depth_reduction() -> None: + state = _make_texture3d_state() + + resp, _ = _handle_request(rpc_request("tex_stats", {"id": 42, "mip": 1, "slice": 2}), state) + assert resp["error"]["code"] == -32001 + assert resp["error"]["message"] == "slice 2 out of range (max: 1)" + + resp, _ = _handle_request(rpc_request("tex_stats", {"id": 42, "mip": 2, "slice": 1}), state) + assert resp["error"]["message"] == "slice 1 out of range (max: 0)" + + # --------------------------------------------------------------------------- # B54: histogram channel length mismatch guard # ---------------------------------------------------------------------------