diff --git a/.github/workflows/Test.yml b/.github/workflows/Test.yml index 8f6e10b..00415e3 100644 --- a/.github/workflows/Test.yml +++ b/.github/workflows/Test.yml @@ -18,5 +18,11 @@ jobs: - name: Building run: zig build - - name: Test + - name: Zig Tests 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 diff --git a/.gitignore b/.gitignore index 0fd2e14..6762740 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .cache/ .zig-cache/ zig-out/ +cts_report/ scripts/__pycache__/ *.o .gdb_history diff --git a/build.zig b/build.zig index 5c1e15e..d702b9f 100644 --- a/build.zig +++ b/build.zig @@ -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 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); if (gdb) { - run.addArg("gdb"); 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-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-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; 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})); } - 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 "" })); - 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; } diff --git a/build.zig.zon b/build.zig.zon index c06560b..9f12aa9 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -26,8 +26,8 @@ .hash = "zdt-0.8.1-xr0_vAxUDwCJRDh9pcAS_mdZBIsvcGTtN-K8JJSWY4I6", }, .cts_bin = .{ - .url = "git+https://github.com/Kbz-8/Vulkan-CTS-bin#2fa3e9310a627c13ba512b5781284f1b1481a938", - .hash = "N-V-__8AAIxh3gbzEZcY5tBSA2BSVhLyCyOG0tnAdYviHn99", + .url = "git+https://github.com/Kbz-8/Vulkan-CTS-bin#b2059d1fb009bfe8c9d0a57df34fd725a56b2526", + .hash = "N-V-__8AAOSUwAbM3LLC9Cmy2b6-ewlLls4afNu4g97n-Xue", }, .cpuinfo = .{ .url = "git+https://github.com/Kbz-8/cpuinfo-zig#77f82a1248194e7fb706967343c66021f8522766", diff --git a/scripts/cts_report_to_html.py b/scripts/cts_report_to_html.py index f697a1e..1d04281 100644 --- a/scripts/cts_report_to_html.py +++ b/scripts/cts_report_to_html.py @@ -9,11 +9,14 @@ https://github.com/ArthurVasseur/Vkd/blob/main/scripts/cts_report.py import sys import re import os +import math import xml.etree.ElementTree as ET import pandas as pd from datetime import datetime from collections import Counter +PAGE_SIZE = 100 + def parse_raw_log(log_text: str): """Extract XML blocks from a raw CTS log file.""" pattern = re.compile( @@ -193,6 +196,64 @@ def sin_approx(angle): import math 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'First') + links.append( + f'Prev' + ) + else: + links.append('First') + links.append('Prev') + + # 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('') + + for p in range(start_page, end_page + 1): + if p == page_num: + links.append(f'{p}') + else: + links.append( + f'{p}' + ) + + # Ellipsis after + if end_page < num_pages: + links.append('') + + # Next / Last + if page_num < num_pages: + links.append( + f'Next' + ) + links.append( + f'Last' + ) + else: + links.append('Next') + links.append('Last') + + return f""" + + """ + def main(): if len(sys.argv) != 3: print("Usage: cts_report.py ") @@ -224,6 +285,9 @@ def main(): df = pd.DataFrame(rows) + total_tests = len(df) + num_pages = math.ceil(total_tests / PAGE_SIZE) + # Calculate statistics before converting status to HTML stats = calculate_statistics(df) @@ -245,25 +309,44 @@ def main(): else: duration_str = f"{stats['total_duration_ms']:.2f}ms" - table_html = df.to_html( - index=False, - escape=False, - justify="center", - border=0, - classes="cts-table", - table_id="results-table" - ) + base_output = os.path.splitext(output_path)[0] # e.g. "report" - # Replace placeholders with actual formatted messages - for i, msg in enumerate(formatted_messages): - table_html = table_html.replace(f"__MSG_PLACEHOLDER_{i}__", msg) + for page_index in range(num_pages): + start = page_index * PAGE_SIZE + 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""" -Vulkan CTS Report +Vulkan CTS Report{page_title_suffix} @@ -701,22 +844,18 @@ body {{ {pie_chart_svg} -
- -
+ {pagination_nav}
{table_html}
@@ -755,9 +894,11 @@ function filterTable() {{ """ + page_output_path = f"cts_report/{base_output}_page_{page_num}.html" - with open(output_path, "w", encoding="utf-8") as f: - f.write(html) + os.makedirs(os.path.dirname(page_output_path), exist_ok=True) + 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"\n--- Test Statistics ---") diff --git a/scripts/wrap_alway_success.sh b/scripts/wrap_alway_success.sh deleted file mode 100755 index 24e0c99..0000000 --- a/scripts/wrap_alway_success.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -"$@" -exit 0 diff --git a/src/soft/SoftDeviceMemory.zig b/src/soft/SoftDeviceMemory.zig index 9b02f8a..a5b3989 100644 --- a/src/soft/SoftDeviceMemory.zig +++ b/src/soft/SoftDeviceMemory.zig @@ -21,6 +21,8 @@ pub fn create(device: *SoftDevice, allocator: std.mem.Allocator, size: vk.Device .destroy = destroy, .map = map, .unmap = unmap, + .flushRange = flushRange, + .invalidateRange = invalidateRange, }; self.* = .{ @@ -37,6 +39,20 @@ pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void { 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 { const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); if (offset >= self.data.len or (size != vk.WHOLE_SIZE and offset + size > self.data.len)) { diff --git a/src/vulkan/DeviceMemory.zig b/src/vulkan/DeviceMemory.zig index 688ca12..ee8a92c 100644 --- a/src/vulkan/DeviceMemory.zig +++ b/src/vulkan/DeviceMemory.zig @@ -16,6 +16,8 @@ vtable: *const VTable, pub const VTable = struct { 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, 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); } +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 { return self.vtable.map(self, offset, size); } diff --git a/src/vulkan/lib_vulkan.zig b/src/vulkan/lib_vulkan.zig index 9e8b9ad..eb41992 100644 --- a/src/vulkan/lib_vulkan.zig +++ b/src/vulkan/lib_vulkan.zig @@ -1103,15 +1103,13 @@ pub export fn strollFlushMappedMemoryRanges(p_device: vk.Device, count: u32, p_r entryPointBeginLogTrace(.vkFlushMappedMemoryRanges); 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(); - - _ = device; - _ = count; - _ = p_ranges; - - return .error_unknown; + for (p_ranges, 0..count) |range, _| { + const memory = NonDispatchable(DeviceMemory).fromHandleObject(range.memory) catch |err| return toVkResult(err); + memory.flushRange(range.offset, range.size) catch |err| return toVkResult(err); + } + return .success; } 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; } -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); 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(); - - _ = device; - _ = count; - _ = ranges; - - return .error_unknown; + for (p_ranges, 0..count) |range, _| { + const memory = NonDispatchable(DeviceMemory).fromHandleObject(range.memory) catch |err| return toVkResult(err); + memory.invalidateRange(range.offset, range.size) catch |err| return toVkResult(err); + } + return .success; } 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 {