Rewrite shaders in WGSL (#172)

- Fixes #141
This commit is contained in:
Jay Oster 2021-06-05 18:40:33 -07:00 committed by GitHub
parent 23da739650
commit 8d77ac2f8b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 146 additions and 167 deletions

View file

@ -1,13 +0,0 @@
# Shaders
The GLSL shader source is not compiled as part of the normal cargo build process. This was a conscious decision sparked by the current state of the ecosystem; compiling GLSL-to-SPIR-V requires a C++ toolchain including CMake, which is an unacceptable constraint for a simple crate providing a pixel buffer.
If you need to modify the GLSL sources, you must also recompile the SPIR-V as well. This can be done with `glslang`, `glslc`, etc.
Compile shaders with `glslangValidator`:
```bash
glslangValidator -V shader.frag && glslangValidator -V shader.vert
```
For more information, see https://github.com/parasyte/pixels/issues/9

View file

@ -0,0 +1,73 @@
// Vertex shader bindings
struct VertexOutput {
[[location(0)]] tex_coord: vec2<f32>;
[[builtin(position)]] position: vec4<f32>;
};
let positions: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
// Upper left triangle
vec2<f32>(-1.0, -1.0),
vec2<f32>(1.0, -1.0),
vec2<f32>(-1.0, 1.0),
// Lower right triangle
vec2<f32>(-1.0, 1.0),
vec2<f32>(1.0, -1.0),
vec2<f32>(1.0, 1.0),
);
let uv: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
// Upper left triangle
vec2<f32>(0.0, 0.0),
vec2<f32>(1.0, 0.0),
vec2<f32>(0.0, 1.0),
// Lower right triangle
vec2<f32>(0.0, 1.0),
vec2<f32>(1.0, 0.0),
vec2<f32>(1.0, 1.0),
);
[[stage(vertex)]]
fn vs_main([[builtin(vertex_index)]] vertex_index: u32) -> VertexOutput {
var out: VertexOutput;
out.tex_coord = uv[vertex_index];
out.position = vec4<f32>(positions[vertex_index], 0.0, 1.0);
return out;
}
// Fragment shader bindings
[[group(0), binding(0)]] var r_tex_color: texture_2d<f32>;
[[group(0), binding(1)]] var r_tex_sampler: sampler;
[[block]] struct Locals {
time: f32;
};
[[group(0), binding(2)]] var r_locals: Locals;
let tau: f32 = 6.283185307179586476925286766559;
let bias: f32 = 0.2376; // Offset the circular time input so it is never 0
// Random functions based on https://thebookofshaders.com/10/
let random_scale: f32 = 43758.5453123;
let random_x: f32 = 12.9898;
let random_y: f32 = 78.233;
fn random(x: f32) -> f32 {
return fract(sin(x) * random_scale);
}
fn random_vec2(st: vec2<f32>) -> f32 {
return random(dot(st, vec2<f32>(random_x, random_y)));
}
[[stage(fragment)]]
fn fs_main([[location(0)]] tex_coord: vec2<f32>) -> [[location(0)]] vec4<f32> {
let sampled_color: vec4<f32> = textureSample(r_tex_color, r_tex_sampler, tex_coord);
let noise_color: vec3<f32> = vec3<f32>(random_vec2(
tex_coord.xy * vec2<f32>(r_locals.time % tau + bias)
));
return vec4<f32>(sampled_color.rgb * noise_color, sampled_color.a);
}

View file

@ -1,38 +0,0 @@
// IMPORTANT: This shader needs to be compiled out-of-band to SPIR-V
// See: https://github.com/parasyte/pixels/issues/9
#version 450
layout(location = 0) in vec2 v_TexCoord;
layout(location = 0) out vec4 outColor;
layout(set = 0, binding = 0) uniform texture2D t_Color;
layout(set = 0, binding = 1) uniform sampler s_Color;
layout(set = 0, binding = 2) uniform Locals {
float u_Time;
};
#define PI 3.1415926535897932384626433832795
#define TAU PI * 2.0
// Offset the circular time input so it is never 0
#define BIAS 0.2376
// Random functions based on https://thebookofshaders.com/10/
#define RANDOM_SCALE 43758.5453123
#define RANDOM_X 12.9898
#define RANDOM_Y 78.233
float random(float x) {
return fract(sin(x) * RANDOM_SCALE);
}
float random_vec2(vec2 st) {
return random(dot(st.xy, vec2(RANDOM_X, RANDOM_Y)));
}
void main() {
vec4 sampledColor = texture(sampler2D(t_Color, s_Color), v_TexCoord.xy);
vec3 noiseColor = vec3(random_vec2(v_TexCoord.xy * vec2(mod(u_Time, TAU) + BIAS)));
outColor = vec4(sampledColor.rgb * noiseColor, sampledColor.a);
}

View file

@ -1,39 +0,0 @@
// IMPORTANT: This shader needs to be compiled out-of-band to SPIR-V
// See: https://github.com/parasyte/pixels/issues/9
#version 450
out gl_PerVertex {
vec4 gl_Position;
};
layout(location = 0) out vec2 v_TexCoord;
const vec2 positions[6] = vec2[6](
// Upper left triangle
vec2(-1.0, -1.0),
vec2(1.0, -1.0),
vec2(-1.0, 1.0),
// Lower right triangle
vec2(-1.0, 1.0),
vec2(1.0, -1.0),
vec2(1.0, 1.0)
);
const vec2 uv[6] = vec2[6](
// Upper left triangle
vec2(0.0, 0.0),
vec2(1.0, 0.0),
vec2(0.0, 1.0),
// Lower right triangle
vec2(0.0, 1.0),
vec2(1.0, 0.0),
vec2(1.0, 1.0)
);
void main() {
v_TexCoord = uv[gl_VertexIndex];
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
}

View file

@ -1,4 +1,5 @@
use pixels::wgpu::{self, util::DeviceExt}; use pixels::wgpu::{self, util::DeviceExt};
use std::borrow::Cow;
pub(crate) struct NoiseRenderer { pub(crate) struct NoiseRenderer {
bind_group: wgpu::BindGroup, bind_group: wgpu::BindGroup,
@ -8,8 +9,13 @@ pub(crate) struct NoiseRenderer {
impl NoiseRenderer { impl NoiseRenderer {
pub(crate) fn new(device: &wgpu::Device, texture_view: &wgpu::TextureView) -> Self { pub(crate) fn new(device: &wgpu::Device, texture_view: &wgpu::TextureView) -> Self {
let vs_module = device.create_shader_module(&wgpu::include_spirv!("../shaders/vert.spv")); let shader = wgpu::ShaderModuleDescriptor {
let fs_module = device.create_shader_module(&wgpu::include_spirv!("../shaders/frag.spv")); label: Some("custom_noise_shader"),
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../shaders/noise.wgsl"))),
flags: wgpu::ShaderFlags::VALIDATION,
};
let vs_module = device.create_shader_module(&shader);
let fs_module = device.create_shader_module(&shader);
// Create a texture sampler with nearest neighbor // Create a texture sampler with nearest neighbor
let sampler = device.create_sampler(&wgpu::SamplerDescriptor { let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
@ -103,7 +109,7 @@ impl NoiseRenderer {
layout: Some(&pipeline_layout), layout: Some(&pipeline_layout),
vertex: wgpu::VertexState { vertex: wgpu::VertexState {
module: &vs_module, module: &vs_module,
entry_point: "main", entry_point: "vs_main",
buffers: &[], buffers: &[],
}, },
primitive: wgpu::PrimitiveState::default(), primitive: wgpu::PrimitiveState::default(),
@ -111,7 +117,7 @@ impl NoiseRenderer {
multisample: wgpu::MultisampleState::default(), multisample: wgpu::MultisampleState::default(),
fragment: Some(wgpu::FragmentState { fragment: Some(wgpu::FragmentState {
module: &fs_module, module: &fs_module,
entry_point: "main", entry_point: "fs_main",
targets: &[wgpu::ColorTargetState { targets: &[wgpu::ColorTargetState {
format: wgpu::TextureFormat::Bgra8UnormSrgb, format: wgpu::TextureFormat::Bgra8UnormSrgb,
blend: Some(wgpu::BlendState { blend: Some(wgpu::BlendState {

View file

@ -1,13 +0,0 @@
# Shaders
The GLSL shader source is not compiled as part of the normal cargo build process. This was a conscious decision sparked by the current state of the ecosystem; compiling GLSL-to-SPIR-V requires a C++ toolchain including CMake, which is an unacceptable constraint for a simple crate providing a pixel buffer.
If you need to modify the GLSL sources, you must also recompile the SPIR-V as well. This can be done with `glslang`, `glslc`, etc.
Compile shaders with `glslangValidator`:
```bash
glslangValidator -V shader.frag && glslangValidator -V shader.vert
```
For more information, see https://github.com/parasyte/pixels/issues/9

Binary file not shown.

53
shaders/scale.wgsl Normal file
View file

@ -0,0 +1,53 @@
// Vertex shader bindings
struct VertexOutput {
[[location(0)]] tex_coord: vec2<f32>;
[[builtin(position)]] position: vec4<f32>;
};
[[block]] struct Locals {
transform: mat4x4<f32>;
};
[[group(0), binding(2)]] var r_locals: Locals;
let positions: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
// Upper left triangle
vec2<f32>(-1.0, -1.0),
vec2<f32>(1.0, -1.0),
vec2<f32>(-1.0, 1.0),
// Lower right triangle
vec2<f32>(-1.0, 1.0),
vec2<f32>(1.0, -1.0),
vec2<f32>(1.0, 1.0),
);
let uv: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
// Upper left triangle
vec2<f32>(0.0, 0.0),
vec2<f32>(1.0, 0.0),
vec2<f32>(0.0, 1.0),
// Lower right triangle
vec2<f32>(0.0, 1.0),
vec2<f32>(1.0, 0.0),
vec2<f32>(1.0, 1.0),
);
[[stage(vertex)]]
fn vs_main([[builtin(vertex_index)]] vertex_index: u32) -> VertexOutput {
var out: VertexOutput;
out.tex_coord = uv[vertex_index];
out.position = r_locals.transform * vec4<f32>(positions[vertex_index], 0.0, 1.0);
return out;
}
// Fragment shader bindings
[[group(0), binding(0)]] var r_tex_color: texture_2d<f32>;
[[group(0), binding(1)]] var r_tex_sampler: sampler;
[[stage(fragment)]]
fn fs_main([[location(0)]] tex_coord: vec2<f32>) -> [[location(0)]] vec4<f32> {
return textureSample(r_tex_color, r_tex_sampler, tex_coord);
}

View file

@ -1,13 +0,0 @@
// IMPORTANT: This shader needs to be compiled out-of-band to SPIR-V
// See: https://github.com/parasyte/pixels/issues/9
#version 450
layout(location = 0) in vec2 v_TexCoord;
layout(location = 0) out vec4 outColor;
layout(set = 0, binding = 0) uniform texture2D t_Color;
layout(set = 0, binding = 1) uniform sampler s_Color;
void main() {
outColor = texture(sampler2D(t_Color, s_Color), v_TexCoord);
}

View file

@ -1,43 +0,0 @@
// IMPORTANT: This shader needs to be compiled out-of-band to SPIR-V
// See: https://github.com/parasyte/pixels/issues/9
#version 450
out gl_PerVertex {
vec4 gl_Position;
};
layout(location = 0) out vec2 v_TexCoord;
layout(set = 0, binding = 2) uniform Locals {
mat4 u_Transform;
};
const vec2 positions[6] = vec2[6](
// Upper left triangle
vec2(-1.0, -1.0),
vec2(1.0, -1.0),
vec2(-1.0, 1.0),
// Lower right triangle
vec2(-1.0, 1.0),
vec2(1.0, -1.0),
vec2(1.0, 1.0)
);
const vec2 uv[6] = vec2[6](
// Upper left triangle
vec2(0.0, 0.0),
vec2(1.0, 0.0),
vec2(0.0, 1.0),
// Lower right triangle
vec2(0.0, 1.0),
vec2(1.0, 0.0),
vec2(1.0, 1.0)
);
void main() {
v_TexCoord = uv[gl_VertexIndex];
gl_Position = u_Transform * vec4(positions[gl_VertexIndex], 0.0, 1.0);
}

Binary file not shown.

View file

@ -1,4 +1,5 @@
use crate::SurfaceSize; use crate::SurfaceSize;
use std::borrow::Cow;
use ultraviolet::Mat4; use ultraviolet::Mat4;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
@ -21,8 +22,13 @@ impl ScalingRenderer {
surface_size: &SurfaceSize, surface_size: &SurfaceSize,
render_texture_format: wgpu::TextureFormat, render_texture_format: wgpu::TextureFormat,
) -> Self { ) -> Self {
let vs_module = device.create_shader_module(&wgpu::include_spirv!("../shaders/vert.spv")); let shader = wgpu::ShaderModuleDescriptor {
let fs_module = device.create_shader_module(&wgpu::include_spirv!("../shaders/frag.spv")); label: Some("pixels_scaling_renderer_shader"),
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../shaders/scale.wgsl"))),
flags: wgpu::ShaderFlags::VALIDATION,
};
let vs_module = device.create_shader_module(&shader);
let fs_module = device.create_shader_module(&shader);
// Create a texture sampler with nearest neighbor // Create a texture sampler with nearest neighbor
let sampler = device.create_sampler(&wgpu::SamplerDescriptor { let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
@ -121,7 +127,7 @@ impl ScalingRenderer {
layout: Some(&pipeline_layout), layout: Some(&pipeline_layout),
vertex: wgpu::VertexState { vertex: wgpu::VertexState {
module: &vs_module, module: &vs_module,
entry_point: "main", entry_point: "vs_main",
buffers: &[], buffers: &[],
}, },
primitive: wgpu::PrimitiveState::default(), primitive: wgpu::PrimitiveState::default(),
@ -129,7 +135,7 @@ impl ScalingRenderer {
multisample: wgpu::MultisampleState::default(), multisample: wgpu::MultisampleState::default(),
fragment: Some(wgpu::FragmentState { fragment: Some(wgpu::FragmentState {
module: &fs_module, module: &fs_module,
entry_point: "main", entry_point: "fs_main",
targets: &[wgpu::ColorTargetState { targets: &[wgpu::ColorTargetState {
format: render_texture_format, format: render_texture_format,
blend: Some(wgpu::BlendState { blend: Some(wgpu::BlendState {