Skip to content
This repository was archived by the owner on Oct 31, 2025. It is now read-only.
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
232 changes: 232 additions & 0 deletions crates/spirv-std/src/float.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::vector::Vector;

/// Abstract trait representing a SPIR-V floating point type.
pub unsafe trait Float: num_traits::Float + crate::scalar::Scalar + Default {
const WIDTH: usize;
Expand All @@ -10,3 +12,233 @@ unsafe impl Float for f32 {
unsafe impl Float for f64 {
const WIDTH: usize = 64;
}

/// Converts two f32 values (floats) into two f16 values (halfs). The result is a u32, with the low
/// 16 bits being the first f16, and the high 16 bits being the second f16.
#[spirv_std_macros::gpu_only]
pub fn vec2_to_f16x2(vec: impl Vector<f32, 2>) -> u32 {
let result;
unsafe {
asm!(
"%glsl = OpExtInstImport \"GLSL.std.450\"",
"%uint = OpTypeInt 32 0",
"%vec = OpLoad _ {vec}",
// 58 = PackHalf2x16
"{result} = OpExtInst %uint %glsl 58 %vec",
vec = in(reg) &vec,
result = out(reg) result,
);
}
result
}

/// Converts two f16 values (halfs) into two f32 values (floats). The parameter is a u32, with the
/// low 16 bits being the first f16, and the high 16 bits being the second f16.
#[spirv_std_macros::gpu_only]
pub fn f16x2_to_vec2<V: Vector<f32, 2>>(int: u32) -> V {
let mut result = Default::default();
unsafe {
asm!(
"%glsl = OpExtInstImport \"GLSL.std.450\"",
"%float = OpTypeFloat 32",
"%vec2 = OpTypeVector %float 2",
// 62 = UnpackHalf2x16
"%result = OpExtInst %vec2 %glsl 62 {int}",
"OpStore {result} %result",
int = in(reg) int,
result = in(reg) &mut result,
);
}
result
}

// We don't have access to a concrete vector type (cfg(feature = "glam") might not be enabled), so
// synth up one manually.
#[cfg_attr(target_arch = "spirv", repr(simd))]
// sometimes dead because on cpu, the `gpu_only` macro nukes the method bodies
#[allow(dead_code)]
#[derive(Default)]
struct F32x2 {
x: f32,
y: f32,
}
unsafe impl Vector<f32, 2> for F32x2 {}

/// Converts an f32 (float) into an f16 (half). The result is a u32, not a u16, due to GPU support
/// for u16 not being universal - the upper 16 bits will always be zero.
#[spirv_std_macros::gpu_only]
pub fn f32_to_f16(float: f32) -> u32 {
vec2_to_f16x2(F32x2 { x: float, y: 0.0 })
}

/// Converts an f16 (half) into an f32 (float). The parameter is a u32, due to GPU support for u16
/// not being universal - the upper 16 bits are ignored.
#[cfg(feature = "glam")]
#[spirv_std_macros::gpu_only]
pub fn f16_to_f32(packed: u32) -> f32 {
f16x2_to_vec2::<F32x2>(packed).x
}

/// Packs a vec4 into 4 8-bit signed integers. See
/// [PackSnorm4x8](https://www.khronos.org/registry/SPIR-V/specs/1.0/GLSL.std.450.html) for exact
/// semantics.
#[spirv_std_macros::gpu_only]
pub fn vec4_to_u8x4_snorm(vec: impl Vector<f32, 4>) -> u32 {
let result;
unsafe {
asm!(
"%glsl = OpExtInstImport \"GLSL.std.450\"",
"%uint = OpTypeInt 32 0",
"%vec = OpLoad _ {vec}",
// 54 = PackSnorm4x8
"{result} = OpExtInst %uint %glsl 54 %vec",
vec = in(reg) &vec,
result = out(reg) result,
);
}
result
}

/// Packs a vec4 into 4 8-bit unsigned integers. See
/// [PackUnorm4x8](https://www.khronos.org/registry/SPIR-V/specs/1.0/GLSL.std.450.html) for exact
/// semantics.
#[spirv_std_macros::gpu_only]
pub fn vec4_to_u8x4_unorm(vec: impl Vector<f32, 4>) -> u32 {
let result;
unsafe {
asm!(
"%glsl = OpExtInstImport \"GLSL.std.450\"",
"%uint = OpTypeInt 32 0",
"%vec = OpLoad _ {vec}",
// 55 = PackUnorm4x8
"{result} = OpExtInst %uint %glsl 55 %vec",
vec = in(reg) &vec,
result = out(reg) result,
);
}
result
}

/// Packs a vec2 into 2 16-bit signed integers. See
/// [PackSnorm2x16](https://www.khronos.org/registry/SPIR-V/specs/1.0/GLSL.std.450.html) for exact
/// semantics.
#[spirv_std_macros::gpu_only]
pub fn vec2_to_u16x2_snorm(vec: impl Vector<f32, 2>) -> u32 {
let result;
unsafe {
asm!(
"%glsl = OpExtInstImport \"GLSL.std.450\"",
"%uint = OpTypeInt 32 0",
"%vec = OpLoad _ {vec}",
// 56 = PackSnorm2x16
"{result} = OpExtInst %uint %glsl 56 %vec",
vec = in(reg) &vec,
result = out(reg) result,
);
}
result
}

/// Packs a vec2 into 2 16-bit unsigned integers. See
/// [PackUnorm2x16](https://www.khronos.org/registry/SPIR-V/specs/1.0/GLSL.std.450.html) for exact
/// semantics.
#[spirv_std_macros::gpu_only]
pub fn vec2_to_u16x2_unorm(vec: impl Vector<f32, 2>) -> u32 {
let result;
unsafe {
asm!(
"%glsl = OpExtInstImport \"GLSL.std.450\"",
"%uint = OpTypeInt 32 0",
"%vec = OpLoad _ {vec}",
// 57 = PackUnorm2x16
"{result} = OpExtInst %uint %glsl 57 %vec",
vec = in(reg) &vec,
result = out(reg) result,
);
}
result
}

/// Unpacks 4 8-bit signed integers into a vec4. See
/// [UnpackSnorm4x8](https://www.khronos.org/registry/SPIR-V/specs/1.0/GLSL.std.450.html) for exact
/// semantics.
#[spirv_std_macros::gpu_only]
pub fn u8x4_to_vec4_snorm<V: Vector<f32, 4>>(int: u32) -> V {
let mut result = Default::default();
unsafe {
asm!(
"%glsl = OpExtInstImport \"GLSL.std.450\"",
"%float = OpTypeFloat 32",
"%vec4 = OpTypeVector %float 4",
// 63 = UnpackSnorm4x8
"%result = OpExtInst %vec4 %glsl 63 {int}",
"OpStore {result} %result",
int = in(reg) int,
result = in(reg) &mut result,
);
}
result
}

/// Unpacks 4 8-bit unsigned integers into a vec4. See
/// [UnpackSnorm4x8](https://www.khronos.org/registry/SPIR-V/specs/1.0/GLSL.std.450.html) for exact
/// semantics.
#[spirv_std_macros::gpu_only]
pub fn u8x4_to_vec4_unorm<V: Vector<f32, 4>>(int: u32) -> V {
let mut result = Default::default();
unsafe {
asm!(
"%glsl = OpExtInstImport \"GLSL.std.450\"",
"%float = OpTypeFloat 32",
"%vec4 = OpTypeVector %float 4",
// 64 = UnpackUnorm4x8
"%result = OpExtInst %vec4 %glsl 64 {int}",
"OpStore {result} %result",
int = in(reg) int,
result = in(reg) &mut result,
);
}
result
}

/// Unpacks 2 16-bit signed integers into a vec2. See
/// [UnpackSnorm2x16](https://www.khronos.org/registry/SPIR-V/specs/1.0/GLSL.std.450.html) for
/// exact semantics.
#[spirv_std_macros::gpu_only]
pub fn u16x2_to_vec2_snorm<V: Vector<f32, 2>>(int: u32) -> V {
let mut result = Default::default();
unsafe {
asm!(
"%glsl = OpExtInstImport \"GLSL.std.450\"",
"%float = OpTypeFloat 32",
"%vec2 = OpTypeVector %float 2",
// 60 = UnpackSnorm2x16
"%result = OpExtInst %vec2 %glsl 60 {int}",
"OpStore {result} %result",
int = in(reg) int,
result = in(reg) &mut result,
);
}
result
}

/// Unpacks 2 16-bit unsigned integers into a vec2. See
/// [UnpackUnorm2x16](https://www.khronos.org/registry/SPIR-V/specs/1.0/GLSL.std.450.html) for
/// exact semantics.
#[spirv_std_macros::gpu_only]
pub fn u16x2_to_vec2_unorm<V: Vector<f32, 2>>(int: u32) -> V {
let mut result = Default::default();
unsafe {
asm!(
"%glsl = OpExtInstImport \"GLSL.std.450\"",
"%float = OpTypeFloat 32",
"%vec2 = OpTypeVector %float 2",
// 61 = UnpackUnorm2x16
"%result = OpExtInst %vec2 %glsl 61 {int}",
"OpStore {result} %result",
int = in(reg) int,
result = in(reg) &mut result,
);
}
result
}
65 changes: 65 additions & 0 deletions tests/ui/lang/f32/packing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Test that various packing methods work.
// build-pass

use spirv_std::float::*;
use spirv_std::glam::{Vec2, Vec4};

#[spirv(fragment)]
pub fn test_vec2_to_f16x2(i: Vec2, o: &mut u32) {
*o = vec2_to_f16x2(i);
}

#[spirv(fragment)]
pub fn test_f16x2_to_vec2(i: u32, o: &mut Vec2) {
*o = f16x2_to_vec2(i);
}

#[spirv(fragment)]
pub fn test_f32_to_f16(i: f32, o: &mut u32) {
*o = f32_to_f16(i);
}

#[spirv(fragment)]
pub fn test_f16_to_f32(i: u32, o: &mut f32) {
*o = f16_to_f32(i);
}

#[spirv(fragment)]
pub fn test_vec4_to_u8x4_snorm(i: Vec4, o: &mut u32) {
*o = vec4_to_u8x4_snorm(i);
}

#[spirv(fragment)]
pub fn test_vec4_to_u8x4_unorm(i: Vec4, o: &mut u32) {
*o = vec4_to_u8x4_unorm(i);
}

#[spirv(fragment)]
pub fn test_vec2_to_u16x2_snorm(i: Vec2, o: &mut u32) {
*o = vec2_to_u16x2_snorm(i);
}

#[spirv(fragment)]
pub fn test_vec2_to_u16x2_unorm(i: Vec2, o: &mut u32) {
*o = vec2_to_u16x2_unorm(i);
}

#[spirv(fragment)]
pub fn test_u8x4_to_vec4_snorm(i: u32, o: &mut Vec4) {
*o = u8x4_to_vec4_snorm(i);
}

#[spirv(fragment)]
pub fn test_u8x4_to_vec4_unorm(i: u32, o: &mut Vec4) {
*o = u8x4_to_vec4_unorm(i);
}

#[spirv(fragment)]
pub fn test_u16x2_to_vec2_snorm(i: u32, o: &mut Vec2) {
*o = u16x2_to_vec2_snorm(i);
}

#[spirv(fragment)]
pub fn test_u16x2_to_vec2_unorm(i: u32, o: &mut Vec2) {
*o = u16x2_to_vec2_unorm(i);
}