improving CTS support

This commit is contained in:
2025-12-07 22:04:42 +01:00
parent fb4d130d9d
commit 4ca671366d
9 changed files with 229 additions and 59 deletions

View File

@@ -18,5 +18,11 @@ jobs:
- name: Building - name: Building
run: zig build run: zig build
- name: Test - name: Zig Tests
run: zig build test-soft run: zig build test-soft
- name: Vulkan Conformance Test Suite
run: zig build test-conformance-soft
- name: Vulkan CTS report
run: zig build test-conformance-soft-result-to-html

1
.gitignore vendored
View File

@@ -1,6 +1,7 @@
.cache/ .cache/
.zig-cache/ .zig-cache/
zig-out/ zig-out/
cts_report/
scripts/__pycache__/ scripts/__pycache__/
*.o *.o
.gdb_history .gdb_history

View File

@@ -189,19 +189,18 @@ fn addCTS(b: *std.Build, target: std.Build.ResolvedTarget, impl: *const Implemen
const cts_exe_path = try cts_exe_name.getPath3(b, null).toString(b.allocator); const cts_exe_path = try cts_exe_name.getPath3(b, null).toString(b.allocator);
const run = b.addSystemCommand(&[_][]const u8{"./scripts/wrap_alway_success.sh"}); const run = b.addSystemCommand(&[_][]const u8{if (gdb) "gdb" else cts_exe_path});
run.step.dependOn(&impl_lib.step); run.step.dependOn(&impl_lib.step);
if (gdb) { if (gdb) {
run.addArg("gdb");
run.addArg("--args"); run.addArg("--args");
run.addArg(cts_exe_path);
} }
run.addArg(cts_exe_path);
run.addArg(b.fmt("--deqp-archive-dir={s}", .{try cts.path("").getPath3(b, null).toString(b.allocator)})); run.addArg(b.fmt("--deqp-archive-dir={s}", .{try cts.path("").getPath3(b, null).toString(b.allocator)}));
run.addArg(b.fmt("--deqp-vk-library-path={s}", .{b.getInstallPath(.lib, impl_lib.out_lib_filename)})); run.addArg(b.fmt("--deqp-vk-library-path={s}", .{b.getInstallPath(.lib, impl_lib.out_lib_filename)}));
run.addArg("--deqp-log-filename=vk-cts-logs.qpa"); run.addArg("--deqp-log-filename=vk-cts-logs.qpa");
run.addArg("--deqp-log-compact=enable"); run.addArg("--deqp-no-program-fail=enable"); // Option added by my fork, doubt it will be merge oneday
var requires_explicit_tests = false; var requires_explicit_tests = false;
if (b.args) |args| { if (b.args) |args| {
@@ -216,14 +215,18 @@ fn addCTS(b: *std.Build, target: std.Build.ResolvedTarget, impl: *const Implemen
run.addArg(b.fmt("--deqp-caselist-file={s}", .{mustpass})); run.addArg(b.fmt("--deqp-caselist-file={s}", .{mustpass}));
} }
const run_to_xml = b.addSystemCommand(&[_][]const u8{ "python", "./scripts/cts_logs_to_xml.py", "./vk-cts-logs.qpa", "./vk-cts-logs.xml" });
run_to_xml.step.dependOn(&run.step);
const run_to_report = b.addSystemCommand(&[_][]const u8{ "python", "./scripts/cts_report_to_html.py", "./vk-cts-logs.xml", "./vk-cts-report.html" });
run_to_report.step.dependOn(&run_to_xml.step);
const run_step = b.step(b.fmt("test-conformance-{s}{s}", .{ impl.name, if (gdb) "-gdb" else "" }), b.fmt("Run Vulkan conformance tests for libvulkan_{s}{s}", .{ impl.name, if (gdb) " within GDB" else "" })); const run_step = b.step(b.fmt("test-conformance-{s}{s}", .{ impl.name, if (gdb) "-gdb" else "" }), b.fmt("Run Vulkan conformance tests for libvulkan_{s}{s}", .{ impl.name, if (gdb) " within GDB" else "" }));
run_step.dependOn(&run_to_report.step); run_step.dependOn(&run.step);
if (!gdb) {
const run_to_xml = b.addSystemCommand(&[_][]const u8{ "python", "./scripts/cts_logs_to_xml.py", "./vk-cts-logs.qpa", "./vk-cts-logs.xml" });
const run_to_report = b.addSystemCommand(&[_][]const u8{ "python", "./scripts/cts_report_to_html.py", "./vk-cts-logs.xml", "vk-cts-report.html" });
run_to_report.step.dependOn(&run_to_xml.step);
const run_report_step = b.step(b.fmt("test-conformance-{s}-result-to-html", .{impl.name}), b.fmt("Run Vulkan conformance tests for libvulkan_{s} with a HTML report", .{impl.name}));
run_report_step.dependOn(&run_to_report.step);
}
return &run.step; return &run.step;
} }

View File

@@ -26,8 +26,8 @@
.hash = "zdt-0.8.1-xr0_vAxUDwCJRDh9pcAS_mdZBIsvcGTtN-K8JJSWY4I6", .hash = "zdt-0.8.1-xr0_vAxUDwCJRDh9pcAS_mdZBIsvcGTtN-K8JJSWY4I6",
}, },
.cts_bin = .{ .cts_bin = .{
.url = "git+https://github.com/Kbz-8/Vulkan-CTS-bin#2fa3e9310a627c13ba512b5781284f1b1481a938", .url = "git+https://github.com/Kbz-8/Vulkan-CTS-bin#b2059d1fb009bfe8c9d0a57df34fd725a56b2526",
.hash = "N-V-__8AAIxh3gbzEZcY5tBSA2BSVhLyCyOG0tnAdYviHn99", .hash = "N-V-__8AAOSUwAbM3LLC9Cmy2b6-ewlLls4afNu4g97n-Xue",
}, },
.cpuinfo = .{ .cpuinfo = .{
.url = "git+https://github.com/Kbz-8/cpuinfo-zig#77f82a1248194e7fb706967343c66021f8522766", .url = "git+https://github.com/Kbz-8/cpuinfo-zig#77f82a1248194e7fb706967343c66021f8522766",

View File

@@ -9,11 +9,14 @@ https://github.com/ArthurVasseur/Vkd/blob/main/scripts/cts_report.py
import sys import sys
import re import re
import os import os
import math
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
import pandas as pd import pandas as pd
from datetime import datetime from datetime import datetime
from collections import Counter from collections import Counter
PAGE_SIZE = 100
def parse_raw_log(log_text: str): def parse_raw_log(log_text: str):
"""Extract <TestCaseResult> XML blocks from a raw CTS log file.""" """Extract <TestCaseResult> XML blocks from a raw CTS log file."""
pattern = re.compile( pattern = re.compile(
@@ -193,6 +196,64 @@ def sin_approx(angle):
import math import math
return math.sin(angle) return math.sin(angle)
def build_pagination(page_num: int, num_pages: int, base_output: str) -> str:
"""
base_output: basename used for files, e.g. 'report' -> report_page_1.html
"""
window = 2 # how many pages before/after the current one to show
links = []
# First / Prev
if page_num > 1:
links.append(f'<a href="{base_output}_page_1.html" class="pag-link">First</a>')
links.append(
f'<a href="{base_output}_page_{page_num-1}.html" class="pag-link">Prev</a>'
)
else:
links.append('<span class="pag-link disabled">First</span>')
links.append('<span class="pag-link disabled">Prev</span>')
# Page range around current
start_page = max(1, page_num - window)
end_page = min(num_pages, page_num + window)
# Ellipsis before
if start_page > 1:
links.append('<span class="pag-ellipsis">…</span>')
for p in range(start_page, end_page + 1):
if p == page_num:
links.append(f'<span class="pag-link active">{p}</span>')
else:
links.append(
f'<a href="{base_output}_page_{p}.html" class="pag-link">{p}</a>'
)
# Ellipsis after
if end_page < num_pages:
links.append('<span class="pag-ellipsis">…</span>')
# Next / Last
if page_num < num_pages:
links.append(
f'<a href="{base_output}_page_{page_num+1}.html" class="pag-link">Next</a>'
)
links.append(
f'<a href="{base_output}_page_{num_pages}.html" class="pag-link">Last</a>'
)
else:
links.append('<span class="pag-link disabled">Next</span>')
links.append('<span class="pag-link disabled">Last</span>')
return f"""
<nav class="pagination">
<span class="pagination-summary">Page {page_num} of {num_pages}</span>
<div class="pagination-links">
{' '.join(links)}
</div>
</nav>
"""
def main(): def main():
if len(sys.argv) != 3: if len(sys.argv) != 3:
print("Usage: cts_report.py <input_log_or_xml> <output_html>") print("Usage: cts_report.py <input_log_or_xml> <output_html>")
@@ -224,6 +285,9 @@ def main():
df = pd.DataFrame(rows) df = pd.DataFrame(rows)
total_tests = len(df)
num_pages = math.ceil(total_tests / PAGE_SIZE)
# Calculate statistics before converting status to HTML # Calculate statistics before converting status to HTML
stats = calculate_statistics(df) stats = calculate_statistics(df)
@@ -245,25 +309,44 @@ def main():
else: else:
duration_str = f"{stats['total_duration_ms']:.2f}ms" duration_str = f"{stats['total_duration_ms']:.2f}ms"
table_html = df.to_html( base_output = os.path.splitext(output_path)[0] # e.g. "report"
index=False,
escape=False,
justify="center",
border=0,
classes="cts-table",
table_id="results-table"
)
# Replace placeholders with actual formatted messages for page_index in range(num_pages):
for i, msg in enumerate(formatted_messages): start = page_index * PAGE_SIZE
table_html = table_html.replace(f"__MSG_PLACEHOLDER_{i}__", msg) end = min(start + PAGE_SIZE, total_tests)
df_page = df.iloc[start:end]
html = f""" # recreate placeholders & table for this page
formatted_messages_page = formatted_messages[start:end]
df_page["Message"] = [f"__MSG_PLACEHOLDER_{i}__" for i in range(start, end)]
table_html = df_page.to_html(
index=False,
escape=False,
justify="center",
border=0,
classes="cts-table",
table_id="results-table"
)
# Replace placeholders for this chunk
for i in range(start, end):
table_html = table_html.replace(
f"__MSG_PLACEHOLDER_{i}__", formatted_messages[i]
)
# Page numbering (1-based for humans)
page_num = page_index + 1
page_title_suffix = f" Page {page_num}/{num_pages}"
# Simple HTML navigation (pure HTML, no JS)
pagination_nav = build_pagination(page_num, num_pages, base_output)
page_html = f"""
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>Vulkan CTS Report</title> <title>Vulkan CTS Report{page_title_suffix}</title>
<style> <style>
:root {{ :root {{
--bg: #0f172a; --bg: #0f172a;
@@ -637,6 +720,66 @@ body {{
.message-pre::-webkit-scrollbar-thumb:hover {{ .message-pre::-webkit-scrollbar-thumb:hover {{
background: rgba(148, 163, 184, 0.5); background: rgba(148, 163, 184, 0.5);
}} }}
.pagination {{
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin: 12px 0;
font-size: 0.85rem;
}}
.pagination-summary {{
color: var(--text-muted);
}}
.pagination-links {{
display: flex;
flex-wrap: wrap;
gap: 4px;
}}
.pag-link,
.pag-ellipsis {{
display: inline-block;
padding: 4px 10px;
border-radius: 999px;
font-size: 0.85rem;
}}
.pag-link {{
text-decoration: none;
border: 1px solid rgba(148, 163, 184, 0.35);
color: var(--text-muted);
background: rgba(15, 23, 42, 0.9);
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}}
.pag-link:hover {{
background: var(--accent-soft);
border-color: var(--accent);
color: var(--accent-strong);
}}
.pag-link.active {{
background: var(--accent-soft);
border-color: var(--accent);
color: var(--accent-strong);
cursor: default;
}}
.pag-link.disabled {{
opacity: 0.4;
border-style: dashed;
cursor: not-allowed;
pointer-events: none;
}}
.pag-ellipsis {{
color: var(--text-muted);
padding: 4px 2px;
}}
</style> </style>
</head> </head>
<body> <body>
@@ -701,22 +844,18 @@ body {{
{pie_chart_svg} {pie_chart_svg}
</div> </div>
<div class="search-container"> {pagination_nav}
<input
type="text"
id="search-input"
class="search-input"
placeholder="Search test cases..."
onkeyup="filterTable()"
/>
</div>
<div class="table-wrapper"> <div class="table-wrapper">
{table_html} {table_html}
</div> </div>
<div class="footer-note"> <div class="footer-note">
Generated by <code>cts_report.py</code> at {generation_time} Generated by
<a href="https://github.com/Kbz-8/VulkanDriver/blob/master/scripts/cts_report_to_html.py">
<code>cts_report.py</code>
</a>
at {generation_time}
</div> </div>
</div> </div>
</div> </div>
@@ -755,9 +894,11 @@ function filterTable() {{
</body> </body>
</html> </html>
""" """
page_output_path = f"cts_report/{base_output}_page_{page_num}.html"
with open(output_path, "w", encoding="utf-8") as f: os.makedirs(os.path.dirname(page_output_path), exist_ok=True)
f.write(html) with open(page_output_path, "w", encoding="utf-8") as f:
f.write(page_html)
print(f"[OK] HTML report saved to: {output_path}") print(f"[OK] HTML report saved to: {output_path}")
print(f"\n--- Test Statistics ---") print(f"\n--- Test Statistics ---")

View File

@@ -1,3 +0,0 @@
#!/bin/sh
"$@"
exit 0

View File

@@ -21,6 +21,8 @@ pub fn create(device: *SoftDevice, allocator: std.mem.Allocator, size: vk.Device
.destroy = destroy, .destroy = destroy,
.map = map, .map = map,
.unmap = unmap, .unmap = unmap,
.flushRange = flushRange,
.invalidateRange = invalidateRange,
}; };
self.* = .{ self.* = .{
@@ -37,6 +39,20 @@ pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
allocator.destroy(self); allocator.destroy(self);
} }
pub fn flushRange(interface: *Interface, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!void {
// No-op, host and device memory are the same for software driver
_ = interface;
_ = offset;
_ = size;
}
pub fn invalidateRange(interface: *Interface, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!void {
// No-op, host and device memory are the same for software driver
_ = interface;
_ = offset;
_ = size;
}
pub fn map(interface: *Interface, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!?*anyopaque { pub fn map(interface: *Interface, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!?*anyopaque {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
if (offset >= self.data.len or (size != vk.WHOLE_SIZE and offset + size > self.data.len)) { if (offset >= self.data.len or (size != vk.WHOLE_SIZE and offset + size > self.data.len)) {

View File

@@ -16,6 +16,8 @@ vtable: *const VTable,
pub const VTable = struct { pub const VTable = struct {
destroy: *const fn (*Self, std.mem.Allocator) void, destroy: *const fn (*Self, std.mem.Allocator) void,
flushRange: *const fn (*Self, vk.DeviceSize, vk.DeviceSize) VkError!void,
invalidateRange: *const fn (*Self, vk.DeviceSize, vk.DeviceSize) VkError!void,
map: *const fn (*Self, vk.DeviceSize, vk.DeviceSize) VkError!?*anyopaque, map: *const fn (*Self, vk.DeviceSize, vk.DeviceSize) VkError!?*anyopaque,
unmap: *const fn (*Self) void, unmap: *const fn (*Self) void,
}; };
@@ -34,6 +36,14 @@ pub inline fn destroy(self: *Self, allocator: std.mem.Allocator) void {
self.vtable.destroy(self, allocator); self.vtable.destroy(self, allocator);
} }
pub inline fn flushRange(self: *Self, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!void {
try self.vtable.flushRange(self, offset, size);
}
pub inline fn invalidateRange(self: *Self, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!void {
try self.vtable.invalidateRange(self, offset, size);
}
pub inline fn map(self: *Self, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!?*anyopaque { pub inline fn map(self: *Self, offset: vk.DeviceSize, size: vk.DeviceSize) VkError!?*anyopaque {
return self.vtable.map(self, offset, size); return self.vtable.map(self, offset, size);
} }

View File

@@ -1103,15 +1103,13 @@ pub export fn strollFlushMappedMemoryRanges(p_device: vk.Device, count: u32, p_r
entryPointBeginLogTrace(.vkFlushMappedMemoryRanges); entryPointBeginLogTrace(.vkFlushMappedMemoryRanges);
defer entryPointEndLogTrace(); defer entryPointEndLogTrace();
const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); Dispatchable(Device).checkHandleValidity(p_device) catch |err| return toVkResult(err);
notImplementedWarning(); for (p_ranges, 0..count) |range, _| {
const memory = NonDispatchable(DeviceMemory).fromHandleObject(range.memory) catch |err| return toVkResult(err);
_ = device; memory.flushRange(range.offset, range.size) catch |err| return toVkResult(err);
_ = count; }
_ = p_ranges; return .success;
return .error_unknown;
} }
pub export fn strollFreeCommandBuffers(p_device: vk.Device, p_pool: vk.CommandPool, count: u32, p_cmds: [*]const vk.CommandBuffer) callconv(vk.vulkan_call_conv) void { pub export fn strollFreeCommandBuffers(p_device: vk.Device, p_pool: vk.CommandPool, count: u32, p_cmds: [*]const vk.CommandBuffer) callconv(vk.vulkan_call_conv) void {
@@ -1324,19 +1322,17 @@ pub export fn strollGetRenderAreaGranularity(p_device: vk.Device, p_pass: vk.Ren
_ = granularity; _ = granularity;
} }
pub export fn strollInvalidateMappedMemoryRanges(p_device: vk.Device, count: u32, ranges: [*]const vk.MappedMemoryRange) callconv(vk.vulkan_call_conv) vk.Result { pub export fn strollInvalidateMappedMemoryRanges(p_device: vk.Device, count: u32, p_ranges: [*]const vk.MappedMemoryRange) callconv(vk.vulkan_call_conv) vk.Result {
entryPointBeginLogTrace(.vkInvalidateMappedMemoryRanges); entryPointBeginLogTrace(.vkInvalidateMappedMemoryRanges);
defer entryPointEndLogTrace(); defer entryPointEndLogTrace();
const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); Dispatchable(Device).checkHandleValidity(p_device) catch |err| return toVkResult(err);
notImplementedWarning(); for (p_ranges, 0..count) |range, _| {
const memory = NonDispatchable(DeviceMemory).fromHandleObject(range.memory) catch |err| return toVkResult(err);
_ = device; memory.invalidateRange(range.offset, range.size) catch |err| return toVkResult(err);
_ = count; }
_ = ranges; return .success;
return .error_unknown;
} }
pub export fn strollMapMemory(p_device: vk.Device, p_memory: vk.DeviceMemory, offset: vk.DeviceSize, size: vk.DeviceSize, _: vk.MemoryMapFlags, pp_data: *?*anyopaque) callconv(vk.vulkan_call_conv) vk.Result { pub export fn strollMapMemory(p_device: vk.Device, p_memory: vk.DeviceMemory, offset: vk.DeviceSize, size: vk.DeviceSize, _: vk.MemoryMapFlags, pp_data: *?*anyopaque) callconv(vk.vulkan_call_conv) vk.Result {