157 lines
4.9 KiB
Plaintext
157 lines
4.9 KiB
Plaintext
Title: Vectorized Axis-Aligned Rect Ops
|
|
Brief: Small detour in making rect type and operations on it in SIMD semantics.
|
|
Date: 1695570693
|
|
Tags: Programming, Zig, Optimization
|
|
CSS: /style.css
|
|
|
|
### Code ###
|
|
Zig's `@shuffle` makes it rather arcane to look at, so be prepared.
|
|
|
|
```zig
|
|
pub fn RectSIMD(comptime T: type) type {
|
|
return struct {
|
|
xyxy: @Vector(4, T),
|
|
|
|
pub fn isPointWithin(self: @This(), p: @Vector(2, T)) bool {
|
|
const q = @shuffle(T, p, self.xyxy, [4]i32{ -1, -2, 0, 1 });
|
|
const w = @shuffle(T, p, self.xyxy, [4]i32{ 0, 1, -3, -4 });
|
|
return @reduce(.And, q <= w);
|
|
}
|
|
|
|
pub fn isRectWithin(self: @This(), a: @This()) bool {
|
|
const q = @shuffle(T, a.xyxy, self.xyxy, [8]i32{ 0, 1, 2, 3, -1, -2, -1, -2 });
|
|
const w = @shuffle(T, a.xyxy, self.xyxy, [8]i32{ -3, -4, -3, -4, 0, 1, 2, 3 });
|
|
return @reduce(.And, q <= w);
|
|
}
|
|
|
|
// todo: Handle zero area cases?
|
|
pub fn isRectIntersecting(self: @This(), a: @This()) bool {
|
|
const q = @shuffle(T, a.xyxy, self.xyxy, [4]i32{ 0, 1, -1, -2 });
|
|
const w = @shuffle(T, a.xyxy, self.xyxy, [4]i32{ -3, -4, 2, 3 });
|
|
return @reduce(.And, q <= w);
|
|
}
|
|
};
|
|
}
|
|
```
|
|
|
|
### Assembly ###
|
|
This is produced by godbolt, which apparently has AVX512 extensions, so, it's extremely compact.
|
|
|
|
Note: Calling prelude and outro are omitted, with inlining you can expect it looking similarly.
|
|
Zig calling convention is used, which is roughly equal to C's static marked procedure.
|
|
|
|
For 32bit floating point:
|
|
```asm
|
|
"example.RectSIMD(f32).isPointWithin":
|
|
vmovaps xmm2, xmm0
|
|
vmovapd xmm1, xmmword ptr [rdi]
|
|
vunpcklpd xmm0, xmm1, xmm2
|
|
vblendpd xmm1, xmm1, xmm2, 1
|
|
vcmpleps k0, xmm0, xmm1
|
|
kmovd eax, k0
|
|
sub al, 15
|
|
sete al
|
|
|
|
"example.RectSIMD(f32).isRectWithin":
|
|
vmovaps xmm2, xmmword ptr [rsi]
|
|
vmovaps xmm0, xmm2
|
|
vmovddup xmm1, qword ptr [rdi]
|
|
vinsertf128 ymm0, ymm0, xmm1, 1
|
|
vmovddup xmm3, qword ptr [rdi + 8]
|
|
vmovaps xmm1, xmm3
|
|
vinsertf128 ymm1, ymm1, xmm2, 1
|
|
vcmpleps k0, ymm0, ymm1
|
|
kortestb k0, k0
|
|
setb al
|
|
|
|
"example.RectSIMD(f32).isRectIntersecting":
|
|
vmovapd xmm2, xmmword ptr [rsi]
|
|
vmovapd xmm1, xmmword ptr [rdi]
|
|
vunpcklpd xmm0, xmm2, xmm1
|
|
vunpckhpd xmm1, xmm1, xmm2
|
|
vcmpleps k0, xmm0, xmm1
|
|
kmovd eax, k0
|
|
sub al, 15
|
|
sete al
|
|
```
|
|
|
|
For 32bit signed integers it fares amazing too:
|
|
```asm
|
|
"example.RectSIMD(i32).isPointWithin":
|
|
vmovaps xmm1, xmm0
|
|
vmovdqa xmm2, xmmword ptr [rdi]
|
|
vpunpcklqdq xmm0, xmm2, xmm1
|
|
vpblendd xmm1, xmm1, xmm2, 12
|
|
vpcmpled k0, xmm0, xmm1
|
|
kmovd eax, k0
|
|
sub al, 15
|
|
sete al
|
|
|
|
"example.RectSIMD(i32).isRectWithin":
|
|
vmovdqa xmm2, xmmword ptr [rsi]
|
|
vmovaps xmm0, xmm2
|
|
vpbroadcastq xmm1, qword ptr [rdi]
|
|
vinserti128 ymm0, ymm0, xmm1, 1
|
|
vpbroadcastq xmm3, qword ptr [rdi + 8]
|
|
vmovaps xmm1, xmm3
|
|
vinserti128 ymm1, ymm1, xmm2, 1
|
|
vpcmpled k0, ymm0, ymm1
|
|
kortestb k0, k0
|
|
setb al
|
|
|
|
"example.RectSIMD(i32).isRectIntersecting":
|
|
vmovdqa xmm2, xmmword ptr [rsi]
|
|
vmovdqa xmm1, xmmword ptr [rdi]
|
|
vpunpcklqdq xmm0, xmm2, xmm1
|
|
vpunpckhqdq xmm1, xmm1, xmm2
|
|
vpcmpled k0, xmm0, xmm1
|
|
kmovd eax, k0
|
|
sub al, 15
|
|
sete al
|
|
```
|
|
|
|
64bit floating point:
|
|
```asm
|
|
"example.RectSIMD(f64).isPointWithin":
|
|
vmovaps xmm3, xmm0
|
|
vmovapd ymm1, ymmword ptr [rdi]
|
|
vinsertf128 ymm0, ymm1, xmm3, 1
|
|
vmovaps xmm2, xmm3
|
|
vblendpd ymm1, ymm1, ymm2, 3
|
|
vcmplepd k0, ymm0, ymm1
|
|
kmovd eax, k0
|
|
sub al, 15
|
|
sete al
|
|
|
|
"example.RectSIMD(f64).isRectWithin":
|
|
vmovapd ymm2, ymmword ptr [rsi]
|
|
vmovapd ymm1, ymmword ptr [rdi]
|
|
vmovaps ymm0, ymm2
|
|
vpermpd ymm3, ymm1, 68
|
|
vinsertf64x4 zmm0, zmm0, ymm3, 1
|
|
vpermpd ymm3, ymm1, 238
|
|
vmovaps ymm1, ymm3
|
|
vinsertf64x4 zmm1, zmm1, ymm2, 1
|
|
vcmplepd k0, zmm0, zmm1
|
|
kortestb k0, k0
|
|
setb al
|
|
|
|
"example.RectSIMD(f64).isRectIntersecting":
|
|
vmovapd ymm2, ymmword ptr [rsi]
|
|
vmovapd ymm1, ymmword ptr [rdi]
|
|
vperm2f128 ymm0, ymm2, ymm1, 32
|
|
vperm2f128 ymm1, ymm1, ymm2, 49
|
|
vcmplepd k0, ymm0, ymm1
|
|
kmovd eax, k0
|
|
sub al, 15
|
|
sete al
|
|
```
|
|
|
|
AVX512 makes it so that there's no big penalty for double precision types, which is nice.
|
|
|
|
### Edits ###
|
|
- Reordered to use packed vectors without swizzling when possible.
|
|
- Eliminated redundant computations.
|
|
- Calling convention notice.
|
|
|