Skip to content

Commit

Permalink
Fix validation error in WGSL shader
Browse files Browse the repository at this point in the history
- This moves the hardcoded vertex positions and texture coordinates to
  the vertex buffer.
- Replaces the two-triangle quad to 1 full-screen triangle (fixes #180)
- Rewrites the custom shader example to fix a bug with large surface
  textures;
  - The input texture size was used for the output texture, causing the
    purple rectangle to appear very jumpy on large displays in full screen.
- The `ScalingRenderer` now exposes its clipping rectangle. The custom
  shader example uses this for its own clipping rectangle, but it can
  also be used for interacting with the border in general.
  • Loading branch information
parasyte committed Jun 24, 2021
1 parent 1c177fc commit a49f489
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 122 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ include = [
]

[dependencies]
bytemuck = "1.7"
pollster = "0.2"
raw-window-handle = "0.3"
thiserror = "1.0"
Expand Down
1 change: 1 addition & 0 deletions examples/custom-shader/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ optimize = ["log/release_max_level_warn"]
default = ["optimize"]

[dependencies]
bytemuck = "1.7"
env_logger = "0.8"
log = "0.4"
pixels = { path = "../.." }
Expand Down
39 changes: 8 additions & 31 deletions examples/custom-shader/shaders/noise.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,14 @@ struct VertexOutput {
[[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 {
fn vs_main(
[[location(0)]] position: vec2<f32>,
[[location(1)]] tex_coord: vec2<f32>,
) -> VertexOutput {
var out: VertexOutput;
out.tex_coord = uv[vertex_index];
out.position = vec4<f32>(positions[vertex_index], 0.0, 1.0);
out.tex_coord = tex_coord;
out.position = vec4<f32>(position, 0.0, 1.0);
return out;
}

Expand Down Expand Up @@ -64,10 +43,8 @@ fn random_vec2(st: vec2<f32>) -> f32 {

[[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)
));
let sampled_color = textureSample(r_tex_color, r_tex_sampler, tex_coord);
let noise_color = 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);
}
35 changes: 6 additions & 29 deletions examples/custom-shader/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use crate::renderers::NoiseRenderer;
use log::error;
use pixels::{wgpu, Error, Pixels, SurfaceTexture};
use pixels::{Error, Pixels, SurfaceTexture};
use winit::dpi::LogicalSize;
use winit::event::{Event, VirtualKeyCode};
use winit::event_loop::{ControlFlow, EventLoop};
Expand Down Expand Up @@ -44,22 +44,22 @@ fn main() -> Result<(), Error> {
Pixels::new(WIDTH, HEIGHT, surface_texture)?
};
let mut world = World::new();

let mut time = 0.0;
let (scaled_texture, noise_renderer) = create_noise_renderer(&pixels);
let mut noise_renderer = NoiseRenderer::new(pixels.device(), WIDTH, HEIGHT);

event_loop.run(move |event, _, control_flow| {
// Draw the current frame
if let Event::RedrawRequested(_) = event {
world.draw(pixels.get_frame());

let render_result = pixels.render_with(|encoder, render_target, context| {
context.scaling_renderer.render(encoder, &scaled_texture);
let noise_texture = noise_renderer.get_texture_view();
context.scaling_renderer.render(encoder, noise_texture);

noise_renderer.update(&context.queue, time);
time += 0.01;

noise_renderer.render(encoder, render_target);
noise_renderer.render(encoder, render_target, context.scaling_renderer.clip_rect());
});

if render_result
Expand All @@ -82,6 +82,7 @@ fn main() -> Result<(), Error> {
// Resize the window
if let Some(size) = input.window_resized() {
pixels.resize_surface(size.width, size.height);
noise_renderer.resize(pixels.device(), size.width, size.height);
}

// Update internal state and request a redraw
Expand Down Expand Up @@ -138,27 +139,3 @@ impl World {
}
}
}

fn create_noise_renderer(pixels: &Pixels) -> (wgpu::TextureView, NoiseRenderer) {
let device = &pixels.device();

let texture_descriptor = wgpu::TextureDescriptor {
label: None,
size: pixels::wgpu::Extent3d {
width: WIDTH,
height: HEIGHT,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Bgra8UnormSrgb,
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::RENDER_ATTACHMENT,
};
let scaled_texture = device
.create_texture(&texture_descriptor)
.create_view(&wgpu::TextureViewDescriptor::default());
let noise_renderer = NoiseRenderer::new(device, &scaled_texture);

(scaled_texture, noise_renderer)
}
147 changes: 122 additions & 25 deletions examples/custom-shader/src/renderers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,28 @@ use pixels::wgpu::{self, util::DeviceExt};
use std::borrow::Cow;

pub(crate) struct NoiseRenderer {
texture_view: wgpu::TextureView,
sampler: wgpu::Sampler,
bind_group_layout: wgpu::BindGroupLayout,
bind_group: wgpu::BindGroup,
render_pipeline: wgpu::RenderPipeline,
time_buffer: wgpu::Buffer,
vertex_buffer: wgpu::Buffer,
}

impl NoiseRenderer {
pub(crate) fn new(device: &wgpu::Device, texture_view: &wgpu::TextureView) -> Self {
pub(crate) fn new(device: &wgpu::Device, width: u32, height: u32) -> Self {
let shader = wgpu::ShaderModuleDescriptor {
label: Some("custom_noise_shader"),
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../shaders/noise.wgsl"))),
flags: wgpu::ShaderFlags::VALIDATION,
};
let module = device.create_shader_module(&shader);

// Create a texture view that will be used as input
// This will be used as the render target for the default scaling renderer
let texture_view = create_texture_view(device, width, height);

// Create a texture sampler with nearest neighbor
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("NoiseRenderer sampler"),
Expand All @@ -32,6 +40,37 @@ impl NoiseRenderer {
border_color: None,
});

// Create vertex buffer; array-of-array of position and texture coordinates
let vertex_data: [[[f32; 2]; 2]; 3] = [
// One full-screen triangle
// See: https://github.com/parasyte/pixels/issues/180
[[-1.0, -1.0], [0.0, 0.0]],
[[3.0, -1.0], [2.0, 0.0]],
[[-1.0, 3.0], [0.0, 2.0]],
];
let vertex_data_slice = bytemuck::cast_slice(&vertex_data);
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("NoiseRenderer vertex buffer"),
contents: vertex_data_slice,
usage: wgpu::BufferUsage::VERTEX,
});
let vertex_buffer_layout = wgpu::VertexBufferLayout {
array_stride: (vertex_data_slice.len() / vertex_data.len()) as wgpu::BufferAddress,
step_mode: wgpu::InputStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x2,
offset: 0,
shader_location: 0,
},
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x2,
offset: 4 * 2,
shader_location: 1,
},
],
};

// Create uniform buffer
let time_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("NoiseRenderer u_Time"),
Expand Down Expand Up @@ -74,28 +113,13 @@ impl NoiseRenderer {
},
],
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(texture_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: &time_buffer,
offset: 0,
size: None,
}),
},
],
});
let bind_group = create_bind_group(
device,
&bind_group_layout,
&texture_view,
&sampler,
&time_buffer,
);

// Create pipeline
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
Expand All @@ -109,7 +133,7 @@ impl NoiseRenderer {
vertex: wgpu::VertexState {
module: &module,
entry_point: "vs_main",
buffers: &[],
buffers: &[vertex_buffer_layout],
},
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
Expand All @@ -129,12 +153,31 @@ impl NoiseRenderer {
});

Self {
texture_view,
sampler,
bind_group_layout,
bind_group,
render_pipeline,
time_buffer,
vertex_buffer,
}
}

pub(crate) fn get_texture_view(&self) -> &wgpu::TextureView {
&self.texture_view
}

pub(crate) fn resize(&mut self, device: &wgpu::Device, width: u32, height: u32) {
self.texture_view = create_texture_view(device, width, height);
self.bind_group = create_bind_group(
device,
&self.bind_group_layout,
&self.texture_view,
&self.sampler,
&self.time_buffer,
);
}

pub(crate) fn update(&self, queue: &wgpu::Queue, time: f32) {
queue.write_buffer(&self.time_buffer, 0, &time.to_ne_bytes());
}
Expand All @@ -143,6 +186,7 @@ impl NoiseRenderer {
&self,
encoder: &mut wgpu::CommandEncoder,
render_target: &wgpu::TextureView,
clip_rect: (u32, u32, u32, u32),
) {
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("NoiseRenderer render pass"),
Expand All @@ -158,6 +202,59 @@ impl NoiseRenderer {
});
rpass.set_pipeline(&self.render_pipeline);
rpass.set_bind_group(0, &self.bind_group, &[]);
rpass.draw(0..6, 0..1);
rpass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
rpass.set_scissor_rect(clip_rect.0, clip_rect.1, clip_rect.2, clip_rect.3);
rpass.draw(0..3, 0..1);
}
}

fn create_texture_view(device: &wgpu::Device, width: u32, height: u32) -> wgpu::TextureView {
let texture_descriptor = wgpu::TextureDescriptor {
label: None,
size: pixels::wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Bgra8UnormSrgb,
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::RENDER_ATTACHMENT,
};

device
.create_texture(&texture_descriptor)
.create_view(&wgpu::TextureViewDescriptor::default())
}

fn create_bind_group(
device: &wgpu::Device,
bind_group_layout: &wgpu::BindGroupLayout,
texture_view: &wgpu::TextureView,
sampler: &wgpu::Sampler,
time_buffer: &wgpu::Buffer,
) -> pixels::wgpu::BindGroup {
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(texture_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: time_buffer,
offset: 0,
size: None,
}),
},
],
})
}
33 changes: 6 additions & 27 deletions shaders/scale.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,14 @@ struct VertexOutput {
};
[[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 {
fn vs_main(
[[location(0)]] position: vec2<f32>,
[[location(1)]] tex_coord: vec2<f32>,
) -> VertexOutput {
var out: VertexOutput;
out.tex_coord = uv[vertex_index];
out.position = r_locals.transform * vec4<f32>(positions[vertex_index], 0.0, 1.0);
out.tex_coord = tex_coord;
out.position = r_locals.transform * vec4<f32>(position, 0.0, 1.0);
return out;
}

Expand Down
Loading

0 comments on commit a49f489

Please sign in to comment.