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
31 changes: 25 additions & 6 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ jobs:
mkdir binaries
cp zig-out/x86_64-linux/odiff binaries/odiff-linux-x64
cp zig-out/aarch64-linux/odiff binaries/odiff-linux-arm64
cp zig-out/riscv64-linux/odiff binaries/odiff-linux-riscv64
cp zig-out/riscv64-linux-rva23/odiff binaries/odiff-linux-riscv64-rva23
cp zig-out/x86_64-windows/odiff.exe binaries/odiff-windows-x64.exe
cp zig-out/aarch64-windows/odiff.exe binaries/odiff-windows-arm64.exe
cp zig-out/x86_64-macos/odiff binaries/odiff-macos-x64
Expand All @@ -54,6 +56,22 @@ jobs:
path: binaries/odiff-linux-arm64
retention-days: 14

- name: Upload riscv64-linux artifact
uses: actions/upload-artifact@v4
with:
if-no-files-found: error
name: odiff-linux-riscv64
path: binaries/odiff-linux-riscv64
retention-days: 14

- name: Upload riscv64-linux rva23 artifact
uses: actions/upload-artifact@v4
with:
if-no-files-found: error
name: odiff-linux-riscv64-rva23
path: binaries/odiff-linux-riscv64-rva23
retention-days: 14

- name: Upload x86_64-windows artifact
uses: actions/upload-artifact@v4
with:
Expand Down Expand Up @@ -86,8 +104,6 @@ jobs:
path: binaries/odiff-macos-arm64
retention-days: 14



test:
name: Run Tests (all targets)
runs-on: ${{ matrix.os }}
Expand All @@ -103,6 +119,9 @@ jobs:
- target: aarch64-macos
os: macos-latest
run_tests: true
- target: riscv64-linux-gnu
os: ubuntu-latest
run_tests: false
defaults:
run:
shell: bash
Expand Down Expand Up @@ -131,15 +150,15 @@ jobs:
run: |
choco install -y nasm

- name: Run tests
- name: Build and run tests
if: matrix.run_tests == true
run: |
zig build test-all --summary all
zig build -Dtarget=${{matrix.target}} test-all --summary all

- name: Skip (runner not available)
- name: Build without running tests
if: matrix.run_tests != true
run: |
echo "Skipping: no hosted runner available for ${{ matrix.target }}"
zig build -Dtarget=${{matrix.target}}

test-avx:
name: Run Tests (AVX x86_64)
Expand Down
34 changes: 34 additions & 0 deletions .github/workflows/riscv-qemu.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: RISC-V QEMU Tests

on:
pull_request:
# this is a VERY SLOW job try to run it only on the riscv C changes
paths:
- "src/rvv.c"

jobs:
riscv:
name: ubuntu-24.04-riscv
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: uraimo/run-on-arch-action@v3
with:
arch: riscv64
distro: ubuntu24.04
githubToken: ${{ github.token }}
dockerRunArgs: |
--volume "${PWD}:/opt/odiff"
install: |
apt-get update
apt-get install -y nasm wget xz-utils
run: |
export TERM=dumb
export NO_COLOR=1

wget https://ziglang.org/download/0.15.1/zig-riscv64-linux-0.15.1.tar.xz
tar -xf zig-riscv64-linux-0.15.1.tar.xz
export PATH="$PWD/zig-riscv64-linux-0.15.1:$PATH"

cd /opt/odiff
zig build test-all -Dtarget=riscv64-linux -Dcpu=generic_rv64+rva23u64 -Doptimize=ReleaseFast
17 changes: 13 additions & 4 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ pub fn build(b: *std.Build) !void {
const root_lib = b.addLibrary(.{
.name = "odiff_lib",
.root_module = lib_mod,
.linkage = if (dynamic) .dynamic else .static,
});
for (integration_tests_with_io) |test_path| {
const integration_test = b.addTest(.{
Expand All @@ -67,7 +68,7 @@ pub fn build(b: *std.Build) !void {
integration_test.root_module.addImport("build_options", build_options_mod);
integration_test.linkLibC();
integration_test.linkLibrary(root_lib);
linkDeps(b, target, optimize, false, integration_test.root_module);
linkDeps(b, target, optimize, dynamic, integration_test.root_module);

const run_integration_test = b.addRunArtifact(integration_test);
integration_test_steps.append(run_integration_test) catch @panic("OOM");
Expand All @@ -81,7 +82,11 @@ pub fn build(b: *std.Build) !void {
.optimize = optimize,
}),
});

pure_test.root_module.addImport("build_options", build_options_mod);
pure_test.addCSourceFiles(.{
.files = &.{"src/rvv.c"},
});

const run_pure_test = b.addRunArtifact(pure_test);
integration_test_steps.append(run_pure_test) catch @panic("OOM");
Expand All @@ -105,11 +110,12 @@ pub fn build(b: *std.Build) !void {
const t = b.resolveTargetQuery(target_query);
_, const odiff_exe = buildOdiff(b, t, optimize, dynamic, build_options_mod);
odiff_exe.root_module.strip = true;
var target_name = try target_query.zigTriple(b.allocator);
if (target_query.cpu_arch == .riscv64 and !target_query.cpu_features_add.isEmpty())
target_name = try std.mem.join(b.allocator, "-", &[_][]const u8{ target_name, "rva23" });
const odiff_output = b.addInstallArtifact(odiff_exe, .{
.dest_dir = .{
.override = .{
.custom = try target_query.zigTriple(b.allocator),
},
.override = .{ .custom = target_name },
},
});
build_ci_step.dependOn(&odiff_output.step);
Expand Down Expand Up @@ -144,6 +150,7 @@ fn buildOdiff(
lib_mod.addCSourceFiles(.{
.files = &.{
"c_bindings/odiff_io.c",
"src/rvv.c",
},
.flags = c_flags.items,
});
Expand Down Expand Up @@ -218,4 +225,6 @@ const build_targets: []const std.Target.Query = &.{
.{ .cpu_arch = .x86_64, .os_tag = .macos },
.{ .cpu_arch = .x86_64, .os_tag = .linux },
.{ .cpu_arch = .x86_64, .os_tag = .windows },
.{ .cpu_arch = .riscv64, .os_tag = .linux },
.{ .cpu_arch = .riscv64, .os_tag = .linux, .cpu_features_add = std.Target.riscv.featureSet(&.{std.Target.riscv.Feature.rva23u64}) },
};
60 changes: 59 additions & 1 deletion src/diff.zig
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ const HAS_AVX512bwvl =
HAS_AVX512f and
std.Target.x86.featureSetHas(builtin.cpu.features, .avx512bw) and
std.Target.x86.featureSetHas(builtin.cpu.features, .avx512vl);
const HAS_NEON = std.Target.aarch64.featureSetHas(builtin.cpu.features, .neon);
const HAS_NEON = builtin.cpu.arch == .aarch64 and std.Target.aarch64.featureSetHas(builtin.cpu.features, .neon);
const HAS_RVV = builtin.cpu.arch == .riscv64 and std.Target.riscv.featureSetHas(builtin.cpu.features, .v);

const RED_PIXEL: u32 = 0xFF0000FF;
const MAX_YIQ_POSSIBLE_DELTA: f64 = 35215.0;
Expand Down Expand Up @@ -145,6 +146,8 @@ pub noinline fn compare(

if (options.enable_asm and HAS_AVX512bwvl and avx_compatible) {
try compareAVX(base, comp, &diff_count);
} else if (HAS_RVV and !options.antialiasing and (options.ignore_regions == null or options.ignore_regions.?.len == 0)) {
try compareRVV(base, comp, &diff_output, &diff_count, if (diff_lines != null) &diff_lines.? else null, ignore_regions, max_delta_f64, options);
} else if (layout_difference) {
// slow path for different layout or weird widths
try compareDifferentLayouts(base, comp, &diff_output, &diff_count, if (diff_lines != null) &diff_lines.? else null, ignore_regions, max_delta_i64, options);
Expand Down Expand Up @@ -363,6 +366,61 @@ extern fn vxdiff(
comp_height: usize,
) u32;

extern fn odiffRVV(
basePtr: [*]const u32,
compPtr: [*]const u32,
size: usize,
max_delta: f32,
diff: ?[*]u32,
diffcol: u32,
) u32;

pub noinline fn compareRVV(base: *const Image, comp: *const Image, diff_output: *?Image, diff_count: *u32, diff_lines: ?*DiffLines, ignore_regions: ?[]struct { u32, u32 }, max_delta: f64, options: DiffOptions) !void {
_ = ignore_regions;
const basePtr: [*]const u32 = @ptrCast(@alignCast(base.data.ptr));
const compPtr: [*]const u32 = @ptrCast(@alignCast(comp.data.ptr));
var diffPtr: ?[*]u32 = null;
if (diff_output.*) |*out| {
diffPtr = @ptrCast(@alignCast(out.data.ptr));
}

const line_by_line = base.width != comp.width or base.height != comp.height or diff_lines != null;
if (line_by_line) {
var y: u32 = 0;
const minHeight = @min(base.height, comp.height);
const minWidth = @min(base.width, comp.width);
while (y < base.height) : (y += 1) {
var cnt: u32 = 0;
var x: u32 = 0;
if (y < minHeight) {
if (diffPtr) |ptr| {
cnt = odiffRVV(basePtr + y * base.width, compPtr + y * comp.width, minWidth, @floatCast(max_delta), ptr + y * base.width, options.diff_pixel);
} else {
cnt = odiffRVV(basePtr + y * base.width, compPtr + y * comp.width, minWidth, @floatCast(max_delta), null, options.diff_pixel);
}
x = minWidth;
}
while (x < base.width) : (x += 1) {
const idx = y * base.width + x;
const alpha = (basePtr[idx] >> 24) & 0xFF;
cnt += if (alpha != 0) 1 else 0;
if (diffPtr) |ptr| {
const old = ptr[idx]; // always read/write for better autovec
ptr[idx] = if (alpha != 0) options.diff_pixel else old;
}
}
if (diff_lines) |lines| {
if (cnt > 0) {
lines.addLine(y);
}
}
diff_count.* += cnt;
}
} else {
diff_count.* += odiffRVV(basePtr, compPtr, base.height * base.width, @floatCast(max_delta), diffPtr, options.diff_pixel);
}
}

pub fn diff(
base: *const Image,
comp: *const Image,
Expand Down
Loading