Files
mirage/tools/test_instance_buffer.py
2025-12-25 13:20:16 +08:00

150 lines
4.7 KiB
Python

#!/usr/bin/env python3
"""
测试 @instance_buffer 注释解析和实例数据生成
这个脚本测试以下功能:
1. parse_shader_annotations 函数正确解析 @instance_buffer 注释
2. detect_instance_buffer 函数正确检测和验证实例缓冲
3. generate_instance_data_struct 函数正确生成 C++ 结构体
运行方式: python -m tools.test_instance_buffer (从项目根目录)
"""
import sys
import os
import re
# 直接在这里定义注释解析函数(避免相对导入问题)
def parse_shader_annotations(source_code: str):
"""解析着色器源码中的特殊注释"""
annotations = {
'instance_buffer_binding': None,
}
lines = source_code.split('\n')
# 用于匹配 @instance_buffer 注释的正则
instance_buffer_comment_re = re.compile(
r'//\s*@instance_buffer|/\*\s*@instance_buffer\s*\*/'
)
# 用于从 layout 声明提取 binding 的正则
binding_re = re.compile(r'binding\s*=\s*(\d+)')
for i, line in enumerate(lines):
if instance_buffer_comment_re.search(line):
# 首先检查同一行
match = binding_re.search(line)
if match:
annotations['instance_buffer_binding'] = int(match.group(1))
continue
# 检查接下来的几行
for j in range(i + 1, min(i + 5, len(lines))):
next_line = lines[j].strip()
if not next_line or next_line.startswith('//'):
continue
match = binding_re.search(next_line)
if match:
annotations['instance_buffer_binding'] = int(match.group(1))
break
if 'layout' not in next_line.lower():
break
if annotations['instance_buffer_binding'] is not None:
break
return annotations
def test_parse_shader_annotations():
"""测试注释解析功能"""
print("=" * 60)
print("测试 1: parse_shader_annotations")
print("=" * 60)
# 测试用例 1: 标准格式
source1 = """
#version 450 core
struct InstanceData {
vec4 rect;
vec4 color;
};
// @instance_buffer
layout(std430, set = 0, binding = 0) readonly buffer InstanceBuffer {
InstanceData instances[];
};
void main() {}
"""
result1 = parse_shader_annotations(source1)
assert result1['instance_buffer_binding'] == 0, f"Expected binding 0, got {result1['instance_buffer_binding']}"
print(f" [PASS] Test case 1: binding = {result1['instance_buffer_binding']}")
# Test case 2: Different binding
source2 = """
// @instance_buffer
layout(std430, set = 1, binding = 5) readonly buffer MyBuffer {
SomeData data[];
};
"""
result2 = parse_shader_annotations(source2)
assert result2['instance_buffer_binding'] == 5, f"Expected binding 5, got {result2['instance_buffer_binding']}"
print(f" [PASS] Test case 2: binding = {result2['instance_buffer_binding']}")
# Test case 3: Block comment format
source3 = """
/* @instance_buffer */
layout(std430, binding = 3) buffer Data { vec4 items[]; };
"""
result3 = parse_shader_annotations(source3)
assert result3['instance_buffer_binding'] == 3, f"Expected binding 3, got {result3['instance_buffer_binding']}"
print(f" [PASS] Test case 3: binding = {result3['instance_buffer_binding']}")
# Test case 4: No @instance_buffer marker
source4 = """
layout(std430, set = 0, binding = 0) buffer SomeBuffer {
vec4 data[];
};
"""
result4 = parse_shader_annotations(source4)
assert result4['instance_buffer_binding'] is None, "Expected None for source without @instance_buffer"
print(" [PASS] Test case 4: Returns None when no marker")
print("\nAll annotation parsing tests passed!\n")
def main():
"""Run all tests"""
print("\n" + "=" * 60)
print(" @instance_buffer Annotation Parsing Test")
print("=" * 60 + "\n")
try:
test_parse_shader_annotations()
print("=" * 60)
print(" All tests PASSED!")
print("=" * 60)
print("\nNote: Data class and code generation tests require")
print(" the full CMake build process. Run:")
print(" cmake --build build --target generate_shader_bindings")
print("=" * 60 + "\n")
return 0
except AssertionError as e:
print(f"\n[FAIL] Test failed: {e}")
return 1
except Exception as e:
print(f"\n[ERROR] Test error: {type(e).__name__}: {e}")
import traceback
traceback.print_exc()
return 1
if __name__ == "__main__":
sys.exit(main())