How to pass 64 bit float uniform to webgl shaders from wasm? - rust

I'm currently attempting to interface with a webgl2 canvas from a WASM program using wasm_bindgen and the web_sys crate. I am passing two vectors into my fragment shader as uniforms however I cant determine how to send them as double precision. It seems like web_sys only has methods for setting f32 values in uniforms.
My understanding is that the WebGl2RenderingContext.uniform2v method supports the javascript Number type which supports double precision floats but it seems there is no analogue in web_sys.
Still fairly new to WebGL and WASM so any insights as to what I'm missing or ideas for workarounds would be appreciated. Cheers!
The data being used for the uniforms is defined on a struct
#[wasm_bindgen]
pub struct CanvasData {
// (snip)
view_min: [f64; 2],
view_max: [f64; 2],
}
And I'm setting the value of the uniforms with
// Set view region uniforms
let vmin = [self.view_min[0] as f32, self.view_min[1] as f32];
let vmax = [self.view_max[0] as f32, self.view_max[1] as f32];
// ^ If possible I don't want to cast these to f32 ^
gl_context
.uniform2fv_with_f32_array(Some(&view_min_uniform_location), &vmin);
gl_context
.uniform2fv_with_f32_array(Some(&view_max_uniform_location), &vmax);
The relevant part of the fragment shader is
#version 300 es
precision highp float;
uniform highp vec2 uViewMin;
uniform highp vec2 uViewMax;
out vec4 outColor;
void main() {
// (snip)
vec2 z = vec2(mix(uViewMin.x, uViewMax.x, 1.0 - uv.x), mix(uViewMin.y, uViewMax.y, uv.y));
// (snip)
}

Related

What are the performance implications of casting between simple structs with the same memory?

I implemented From Point3 for Vertex a simple struct with one field called position that is [f32; 3]. Point3 is marked as #[repr(C)] which I think means that the 3 f32 will be right next to each other in memory, with no padding for alignment. I also think that [f32, 3] will also have no padding for alignment. Is this true? If it is true, does that mean there is a way to "reinterpret" the memory of Point3 as Vertex? If it is not true, what is are the performance implications? I'm sorry if this is not clear, I am asking this question to understand a bit more about the low level parts of Rust.
impl From<cgmath::Point3<f32>> for Vertex {
fn from(vertex: cgmath::Point3<f32>) -> Self {
Vertex {
position: [vertex.x, vertex.y, vertex.z],
}
}
}

nalgebra type annotations for converting Matrix to DMatrix

I'm trying to convert a static matrix into a dynamic matrix using a function. Reason being is that I'm comparing two matrices for unit-testing. Thus the test_value from the test, and the benchmark matrices have to be the same element wise. They will only compare though if they're the same type and sometimes the test_value is a static matrix. I'd like a function that takes in a static matrix type and converts it into a dynamic matrix. So far using the vscode refactor tool I've managed this:
fn matrix_to_dynamic_matrix(matrix: Matrix<f64, Const, Const, ArrayStorage<f64>>) -> Matrix<f64, Dynamic, Dynamic, VecStorage<f64, Dynamic, Dynamic>> {
let matrix_vector:Vec<f64> = matrix.iter().map(|x| x.to_owned()).collect();
let (rows, columns) = matrix.shape();
let matrix_dynamic:DMatrix<f64> = DMatrix::from_vec(rows, columns, matrix_vector);
matrix_dynamic
}
The compiler won't get past the Matrix<f64, Const, Const, ArrayStorage<f64>>. What the right way to take in the statically sized matrix into the function?
Since you don't know size of static matrix use generic size type for your function:
use nalgebra;
use nalgebra::allocator::Allocator;
use nalgebra::DefaultAllocator;
use nalgebra::DimMinimum;
// rust nalgebra convert static matrix to dynamic matrix
fn static_to_dynamic_matrix<R, C>(matrix: nalgebra::OMatrix<f64, R, C>) -> nalgebra::OMatrix<f64, nalgebra::Dynamic, nalgebra::Dynamic>
where
R: nalgebra::Dim + nalgebra::DimMin<C>,
C: nalgebra::Dim,
DefaultAllocator: Allocator<f64, R, C>
{
let mut dynamic_matrix = nalgebra::OMatrix::<f64, nalgebra::Dynamic, nalgebra::Dynamic>::zeros(matrix.nrows(), matrix.ncols());
for i in 0..matrix.nrows() {
for j in 0..matrix.ncols() {
dynamic_matrix[(i, j)] = matrix[(i, j)];
}
}
dynamic_matrix
}
mod tests {
use super::*;
#[test]
fn test_static_to_dynamic_matrix() {
let matrix = nalgebra::OMatrix::<f64, nalgebra::U3, nalgebra::U3>::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
let dynamic_matrix = static_to_dynamic_matrix(matrix);
assert_eq!(dynamic_matrix.nrows(), 3);
assert_eq!(dynamic_matrix.ncols(), 3);
assert_eq!(dynamic_matrix[(0, 0)], 1.0);
assert_eq!(dynamic_matrix[(0, 1)], 2.0);
}
}

Rusttype how to position glyphs vertically

I'm trying to use the rusttype crate to render text. So far it's been fantastic, but I am running into a wall when trying to correctly position individual glyphs.
I am rendering text by rendering each glyph to an RgbaImage (from the image crate, like in the rusttype image example) and rendering it to a quad mesh.
pub struct Glyph {
pub image: image::RgbaImage,
pub glyph: rusttype::PositionedGlyph<'static>,
pub vertical_offset: f32,
}
// &self.font is a rusttype::Font type
pub fn draw_glyph(&self, ch: char, font_size: f32) -> Option<Glyph> {
// Set size
let scale = rusttype::Scale::uniform(font_size);
let v_metrics = self.font.v_metrics(scale);
// Position and scale glyph
let offset = rusttype::point(0.0, 0.0 + v_metrics.ascent);
let glyph = self.font.glyph(ch).scaled(scale).positioned(offset);
// Get glyph dimensions
let bounds = glyph.pixel_bounding_box()?;
let glyph_height = (v_metrics.ascent - v_metrics.descent).ceil() as u32;
let glyph_width = (bounds.max.x - bounds.min.x) as u32;
// Try to align glyphs on a baseline
let offset_y = bounds.height() as f32 - v_metrics.ascent;
// Generate image
let mut image =
image::ImageBuffer::from_pixel(glyph_width, glyph_height, image::Rgba([200; 4]));
glyph.draw(|x, y, v| {
image.put_pixel(x, y, image::Rgba([0 + (v * 255.0) as u8; 4]));
});
Some(Glyph {
glyph: glyph,
image: image,
vertical_offset: offset_y,
})
}
However, without the vertical_offset calculation, I receive the following text:
As you can see, the characters are all over the place.
I need to take into account the ascent and descent size of the font. However, this is where I run into issues.
I can compensate for the ascent using the following calculation:
let bounds = glyph.pixel_bounding_box()?;
let offset_y = bounds.height() as f32 - v_metrics.ascent;
And then shifting the quad meshes downwards by offset_y.
This improves the output when there are no descenders like "g" in the text:
But does not help when there are descending characters involved:
I cannot figure out how the rusttype examples handle this. They use the Font::layout method, but this doesn't have any special vertical align code.
I'm definitely doing something wrong. Please help!
Try compensating for the descent as well:
let offset_y = bounds.height() as f32 - v_metrics.ascent + v_metrics.descent;

Transmuting a #[repr(align(...))] padded struct to a &[u8] without causing UB

In GLSL, a vec3 is required to be aligned on a 16-byte boundary when used in a uniform block. In order to represent this requirement, I have a Rust type defined as follows:
#[repr(C, align(16))]
struct UniformVec3([f32; 3]);
This makes it easier to construct uniform data on the CPU side without having to manually specify padding. However, in order to upload the data to the GPU, the struct has to be transmuted to a &[u8], like this:
let v = UniformVec3([1.0, 0.0, 0.0]);
unsafe {
let as_bytes = std::slice::from_raw_parts(
(&v as *const UniformVec3) as *const u8,
std::size_of::<UniformVec3>(),
);
// do stuff with as_bytes...
}
As it stands, this is undefined behavior because the bit pattern of the four bytes of padding is undefined. My question is: is there some way to initialize this struct (with its current definition!) without invoking UB upon transmuting it to a &[u8]? Or do I have to insert explicit padding in the struct, which will be defined at initialization?

wgpu-rs: Putting a Matrix3 into a vertex shader results in odd behavior but using a Matrix4 works fine

Using wgpu-rs, I'm trying to get a 3x3 cgmath matrix into a shader (compiled using glsl-to-spirv). However, the resulting mat3 in the shader has incorrect data. When I replace the mat3 and Matrix3 with mat4 and Matrix4, everything works fine and the matrix has correct data.
Vertex Shader:
layout(set=0, binding=0) uniform Uniforms {
mat3 camera_transform;
};
Render Loop:
let mut encoder = self.device.create_command_encoder(
&wgpu::CommandEncoderDescriptor {
label: Some("update encoder"),
},
);
let staging_buffer = self.device.create_buffer_with_data(
bytemuck::cast_slice(&[self.uniforms]),
wgpu::BufferUsage::COPY_SRC,
);
encoder.copy_buffer_to_buffer(&staging_buffer, 0, &self.uniform_buffer, 0, std::mem::size_of::<Uniforms>() as wgpu::BufferAddress);
self.queue.submit(&[encoder.finish()]);
// ...
render_pass.set_bind_group(0, &self.uniform_bind_group, &[]);
Uniforms:
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct Uniforms {
pub camera_transform: Matrix3::<f32>,
}
unsafe impl bytemuck::Pod for Uniforms {}
unsafe impl bytemuck::Zeroable for Uniforms {}
impl Uniforms {
pub fn new() -> Self {
Self {
camera_transform: Matrix3::identity(),
}
}
}
This is an open issue in wgpu-rs. Indeed the simplest workaround may be to make your mat3 into a mat4 until it is resolved.
The problem seems to be a mistake of alignment in generating SPIR-V. The actual alignment is:
If the member is a scalar consuming N basic machine units, the base alignment is N.
If the member is a two- or four-component vector with components consuming N basic machine units, the base alignment is 2N or 4N,
respectively.
If the member is a three-component vector with components consuming N basic machine units, the base alignment is 4N.
If the member is an array of scalars or vectors, the base alignment and array stride are set to match the base alignment of a single array
element, according to rules (1), (2), and (3), and rounded up to the
base alignment of a vec4. The array may have padding at the end; the
base offset of the member following the array is rounded up to the
next multiple of the base alignment.
You are in case 4. Having a mat4 should leave no extra padding on the end and not give any possibility for misalignment issues.

Resources