diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d669a2b8..85a8649e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: rust: - stable - beta - - 1.57.0 + - 1.60.0 steps: - name: Checkout sources uses: actions/checkout@v2 @@ -73,7 +73,7 @@ jobs: rust: - stable - beta - - 1.57.0 + - 1.60.0 steps: - name: Checkout sources uses: actions/checkout@v2 diff --git a/Cargo.toml b/Cargo.toml index 8a2efdc3..3c801df0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,11 +19,11 @@ include = [ ] [dependencies] -bytemuck = "1.7" +bytemuck = "1.10" raw-window-handle = "0.4" thiserror = "1.0" -ultraviolet = "0.8" -wgpu = "0.12" +ultraviolet = "0.9" +wgpu = "0.13" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] pollster = "0.2" diff --git a/MSRV.md b/MSRV.md index c21e3a67..e1e73791 100644 --- a/MSRV.md +++ b/MSRV.md @@ -2,6 +2,7 @@ | `pixels` version | `rustc` version | |------------------|-----------------| +| `0.10.0` | `1.60.0` | | `0.9.0` | `1.57.0` | | `0.8.0` | `1.52.0` | | `0.7.0` | `1.52.0` | diff --git a/README.md b/README.md index 629aef64..014ad11d 100644 --- a/README.md +++ b/README.md @@ -65,3 +65,7 @@ $ RUST_LOG=trace cargo run --package minimal-winit --release --no-default-featur ## Comparison with `minifb` The [`minifb`](https://crates.io/crates/minifb) crate shares some similarities with `pixels`; it also allows rapid prototyping of 2D games and emulators. But it requires the use of its own window/GUI management, event loop, and input handling. One of the disadvantages with the `minifb` approach is the lack of hardware acceleration (except on macOS, which uses Metal but is not configurable). An advantage is that it relies on fewer dependencies. + +## Comparison with `softbuffer` + +There is a more recent project called [`softbuffer`](https://github.com/john01dav/softbuffer). It provides similar capabilities to what `pixels` offers, but is intentionally limited to software-only (not hardware-accelerated) rasterization. diff --git a/examples/conway/Cargo.toml b/examples/conway/Cargo.toml index b348c08c..548917b2 100644 --- a/examples/conway/Cargo.toml +++ b/examples/conway/Cargo.toml @@ -10,7 +10,7 @@ optimize = ["log/release_max_level_warn"] default = ["optimize"] [dependencies] -byteorder = "1.3" +byteorder = "1.4" env_logger = "0.9" getrandom = "0.2" line_drawing = "1.0" @@ -18,4 +18,4 @@ log = "0.4" pixels = { path = "../.." } randomize = "3.0" winit = "0.26" -winit_input_helper = "0.11" +winit_input_helper = "0.12" diff --git a/examples/conway/src/main.rs b/examples/conway/src/main.rs index 1c4ed1df..3943d100 100644 --- a/examples/conway/src/main.rs +++ b/examples/conway/src/main.rs @@ -66,7 +66,7 @@ fn main() -> Result<(), Error> { if input.key_pressed(VirtualKeyCode::P) { paused = !paused; } - if input.key_pressed(VirtualKeyCode::Space) { + if input.key_pressed_os(VirtualKeyCode::Space) { // Space is frame-step, so ensure we're paused paused = true; } @@ -127,7 +127,7 @@ fn main() -> Result<(), Error> { if let Some(size) = input.window_resized() { pixels.resize_surface(size.width, size.height); } - if !paused || input.key_pressed(VirtualKeyCode::Space) { + if !paused || input.key_pressed_os(VirtualKeyCode::Space) { life.update(); } window.request_redraw(); diff --git a/examples/custom-shader/Cargo.toml b/examples/custom-shader/Cargo.toml index 880b5444..44b5baed 100644 --- a/examples/custom-shader/Cargo.toml +++ b/examples/custom-shader/Cargo.toml @@ -10,9 +10,9 @@ optimize = ["log/release_max_level_warn"] default = ["optimize"] [dependencies] -bytemuck = "1.7" +bytemuck = "1.10" env_logger = "0.9" log = "0.4" pixels = { path = "../.." } winit = "0.26" -winit_input_helper = "0.11" +winit_input_helper = "0.12" diff --git a/examples/custom-shader/shaders/noise.wgsl b/examples/custom-shader/shaders/noise.wgsl index 8f6c0014..fd7c5219 100644 --- a/examples/custom-shader/shaders/noise.wgsl +++ b/examples/custom-shader/shaders/noise.wgsl @@ -1,13 +1,13 @@ // Vertex shader bindings struct VertexOutput { - [[location(0)]] tex_coord: vec2; - [[builtin(position)]] position: vec4; -}; + @location(0) tex_coord: vec2, + @builtin(position) position: vec4, +} -[[stage(vertex)]] +@vertex fn vs_main( - [[location(0)]] position: vec2, + @location(0) position: vec2, ) -> VertexOutput { var out: VertexOutput; out.tex_coord = fma(position, vec2(0.5, -0.5), vec2(0.5, 0.5)); @@ -17,12 +17,12 @@ fn vs_main( // Fragment shader bindings -[[group(0), binding(0)]] var r_tex_color: texture_2d; -[[group(0), binding(1)]] var r_tex_sampler: sampler; +@group(0) @binding(0) var r_tex_color: texture_2d; +@group(0) @binding(1) var r_tex_sampler: sampler; struct Locals { - time: f32; -}; -[[group(0), binding(2)]] var r_locals: 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 @@ -40,8 +40,8 @@ fn random_vec2(st: vec2) -> f32 { return random(dot(st, vec2(random_x, random_y))); } -[[stage(fragment)]] -fn fs_main([[location(0)]] tex_coord: vec2) -> [[location(0)]] vec4 { +@fragment +fn fs_main(@location(0) tex_coord: vec2) -> @location(0) vec4 { let sampled_color = textureSample(r_tex_color, r_tex_sampler, tex_coord); let noise_color = vec3(random_vec2(tex_coord.xy * vec2(r_locals.time % tau + bias))); diff --git a/examples/custom-shader/src/main.rs b/examples/custom-shader/src/main.rs index 12938e57..537e89b4 100644 --- a/examples/custom-shader/src/main.rs +++ b/examples/custom-shader/src/main.rs @@ -38,14 +38,14 @@ fn main() -> Result<(), Error> { .unwrap() }; + let window_size = window.inner_size(); let mut pixels = { - let window_size = window.inner_size(); let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window); Pixels::new(WIDTH, HEIGHT, surface_texture)? }; let mut world = World::new(); let mut time = 0.0; - let mut noise_renderer = NoiseRenderer::new(&pixels, WIDTH, HEIGHT); + let mut noise_renderer = NoiseRenderer::new(&pixels, window_size.width, window_size.height); event_loop.run(move |event, _, control_flow| { // Draw the current frame diff --git a/examples/custom-shader/src/renderers.rs b/examples/custom-shader/src/renderers.rs index f5e4806d..22121465 100644 --- a/examples/custom-shader/src/renderers.rs +++ b/examples/custom-shader/src/renderers.rs @@ -14,7 +14,7 @@ impl NoiseRenderer { pub(crate) fn new(pixels: &pixels::Pixels, width: u32, height: u32) -> Self { let device = pixels.device(); let shader = wgpu::include_wgsl!("../shaders/noise.wgsl"); - let module = device.create_shader_module(&shader); + 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 @@ -127,14 +127,14 @@ impl NoiseRenderer { fragment: Some(wgpu::FragmentState { module: &module, entry_point: "fs_main", - targets: &[wgpu::ColorTargetState { + targets: &[Some(wgpu::ColorTargetState { format: pixels.render_texture_format(), blend: Some(wgpu::BlendState { color: wgpu::BlendComponent::REPLACE, alpha: wgpu::BlendComponent::REPLACE, }), write_mask: wgpu::ColorWrites::ALL, - }], + })], }), multiview: None, }); @@ -177,14 +177,14 @@ impl NoiseRenderer { ) { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("NoiseRenderer render pass"), - color_attachments: &[wgpu::RenderPassColorAttachment { + color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: render_target, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), store: true, }, - }], + })], depth_stencil_attachment: None, }); rpass.set_pipeline(&self.render_pipeline); diff --git a/examples/imgui-winit/Cargo.toml b/examples/imgui-winit/Cargo.toml index 56945c90..b858ac59 100644 --- a/examples/imgui-winit/Cargo.toml +++ b/examples/imgui-winit/Cargo.toml @@ -12,9 +12,9 @@ default = ["optimize"] [dependencies] env_logger = "0.9" imgui = "0.8" -imgui-wgpu = "0.19" +imgui-wgpu = "0.20" imgui-winit-support = { version = "0.8", default-features = false, features = ["winit-26"] } log = "0.4" pixels = { path = "../.." } winit = "0.26" -winit_input_helper = "0.11" +winit_input_helper = "0.12" diff --git a/examples/imgui-winit/src/gui.rs b/examples/imgui-winit/src/gui.rs index 90aba9ea..0b854ef2 100644 --- a/examples/imgui-winit/src/gui.rs +++ b/examples/imgui-winit/src/gui.rs @@ -108,14 +108,14 @@ impl Gui { // Render Dear ImGui with WGPU let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("imgui"), - color_attachments: &[wgpu::RenderPassColorAttachment { + color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: render_target, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, store: true, }, - }], + })], depth_stencil_attachment: None, }); diff --git a/examples/invaders/Cargo.toml b/examples/invaders/Cargo.toml index 8b65c192..63a78ee4 100644 --- a/examples/invaders/Cargo.toml +++ b/examples/invaders/Cargo.toml @@ -12,12 +12,12 @@ default = ["optimize"] [dependencies] byteorder = "1.3" env_logger = "0.9" -game-loop = { version = "0.8", features = ["window"] } +game-loop = { version = "0.9", features = ["window"] } getrandom = "0.2" -gilrs = "0.8" +gilrs = "0.9" log = "0.4" pixels = { path = "../.." } randomize = "3.0" simple-invaders = { path = "simple-invaders" } winit = "0.26" -winit_input_helper = "0.11" +winit_input_helper = "0.12" diff --git a/examples/invaders/simple-invaders/src/lib.rs b/examples/invaders/simple-invaders/src/lib.rs index 3f1dc02b..9e59ccd8 100644 --- a/examples/invaders/simple-invaders/src/lib.rs +++ b/examples/invaders/simple-invaders/src/lib.rs @@ -620,7 +620,7 @@ fn make_invader_grid(assets: &Assets) -> Vec>> { } fn next_invader<'a>( - invaders: &'a mut Vec>>, + invaders: &'a mut [Vec>], stepper: &mut Point, ) -> (&'a mut Invader, bool) { let mut is_leader = false; diff --git a/examples/invaders/src/main.rs b/examples/invaders/src/main.rs index 858ad2d5..6a61ed5e 100644 --- a/examples/invaders/src/main.rs +++ b/examples/invaders/src/main.rs @@ -8,10 +8,7 @@ use pixels::{Error, Pixels, SurfaceTexture}; use simple_invaders::{Controls, Direction, World, FPS, HEIGHT, TIME_STEP, WIDTH}; use std::{env, time::Duration}; use winit::{ - dpi::LogicalSize, - event::{Event, VirtualKeyCode}, - event_loop::EventLoop, - window::WindowBuilder, + dpi::LogicalSize, event::VirtualKeyCode, event_loop::EventLoop, window::WindowBuilder, }; use winit_input_helper::WinitInputHelper; @@ -31,8 +28,6 @@ struct Game { gamepad: Option, /// Game pause state. paused: bool, - /// State for key edge detection. - held: [bool; 2], } impl Game { @@ -45,14 +40,10 @@ impl Game { gilrs: Gilrs::new().unwrap(), // XXX: Don't unwrap. gamepad: None, paused: false, - held: [false; 2], } } - fn update_controls(&mut self, event: &Event<()>) { - // Let winit_input_helper collect events to build its state. - self.input.update(event); - + fn update_controls(&mut self) { // Pump the gilrs event loop and find an active gamepad while let Some(gilrs::Event { id, event, .. }) = self.gilrs.next_event() { let pad = self.gilrs.gamepad(id); @@ -67,17 +58,11 @@ impl Game { self.controls = { // Keyboard controls - let held = [ - self.input.key_held(VirtualKeyCode::Pause), - self.input.key_held(VirtualKeyCode::P), - ]; - let mut left = self.input.key_held(VirtualKeyCode::Left); let mut right = self.input.key_held(VirtualKeyCode::Right); - let mut fire = self.input.key_held(VirtualKeyCode::Space); - let mut pause = (held[0] ^ self.held[0] & held[0]) | (held[1] ^ self.held[1] & held[1]); - - self.held = held; + let mut fire = self.input.key_pressed(VirtualKeyCode::Space); + let mut pause = self.input.key_pressed(VirtualKeyCode::Pause) + | self.input.key_pressed(VirtualKeyCode::P); // GamePad controls if let Some(id) = self.gamepad { @@ -85,7 +70,9 @@ impl Game { left |= gamepad.is_pressed(Button::DPadLeft); right |= gamepad.is_pressed(Button::DPadRight); - fire |= gamepad.is_pressed(Button::South); + fire |= gamepad.button_data(Button::South).map_or(false, |button| { + button.is_pressed() && button.counter() == self.gilrs.counter() + }); pause |= gamepad.button_data(Button::Start).map_or(false, |button| { button.is_pressed() && button.counter() == self.gilrs.counter() }); @@ -166,18 +153,21 @@ fn main() -> Result<(), Error> { } }, |g, event| { - // Update controls - g.game.update_controls(&event); - - // Close events - if g.game.input.key_pressed(VirtualKeyCode::Escape) || g.game.input.quit() { - g.exit(); - return; - } - - // Resize the window - if let Some(size) = g.game.input.window_resized() { - g.game.pixels.resize_surface(size.width, size.height); + // Let winit_input_helper collect events to build its state. + if g.game.input.update(event) { + // Update controls + g.game.update_controls(); + + // Close events + if g.game.input.key_pressed(VirtualKeyCode::Escape) || g.game.input.quit() { + g.exit(); + return; + } + + // Resize the window + if let Some(size) = g.game.input.window_resized() { + g.game.pixels.resize_surface(size.width, size.height); + } } }, ); diff --git a/examples/minimal-egui/Cargo.toml b/examples/minimal-egui/Cargo.toml index 1268c6fa..4b804851 100644 --- a/examples/minimal-egui/Cargo.toml +++ b/examples/minimal-egui/Cargo.toml @@ -10,11 +10,11 @@ optimize = ["log/release_max_level_warn"] default = ["optimize"] [dependencies] -egui = "0.16" -egui_wgpu_backend = "0.16" -egui-winit = { version = "0.16", default-features = false, features = ["links"] } +egui = "0.18" +egui_wgpu_backend = "0.18" +egui-winit = { version = "0.18", default-features = false, features = ["links"] } env_logger = "0.9" log = "0.4" pixels = { path = "../.." } winit = "0.26" -winit_input_helper = "0.11" +winit_input_helper = "0.12" diff --git a/examples/minimal-egui/src/gui.rs b/examples/minimal-egui/src/gui.rs index 016bb7d7..4faae44b 100644 --- a/examples/minimal-egui/src/gui.rs +++ b/examples/minimal-egui/src/gui.rs @@ -1,16 +1,17 @@ -use egui::{ClippedMesh, CtxRef}; -use egui_wgpu_backend::{BackendError, RenderPass, ScreenDescriptor}; +use egui::{ClippedPrimitive, Context, TexturesDelta}; +use egui_wgpu_backend::{RenderPass, ScreenDescriptor}; use pixels::{wgpu, PixelsContext}; use winit::window::Window; /// Manages all state required for rendering egui over `Pixels`. pub(crate) struct Framework { // State for egui. - egui_ctx: CtxRef, + egui_ctx: Context, egui_state: egui_winit::State, screen_descriptor: ScreenDescriptor, rpass: RenderPass, - paint_jobs: Vec, + paint_jobs: Vec, + textures: TexturesDelta, // State for the GUI gui: Gui, @@ -25,14 +26,17 @@ struct Gui { impl Framework { /// Create egui. pub(crate) fn new(width: u32, height: u32, scale_factor: f32, pixels: &pixels::Pixels) -> Self { - let egui_ctx = CtxRef::default(); - let egui_state = egui_winit::State::from_pixels_per_point(scale_factor); + let max_texture_size = pixels.device().limits().max_texture_dimension_2d as usize; + + let egui_ctx = Context::default(); + let egui_state = egui_winit::State::from_pixels_per_point(max_texture_size, scale_factor); let screen_descriptor = ScreenDescriptor { physical_width: width, physical_height: height, scale_factor, }; let rpass = RenderPass::new(pixels.device(), pixels.render_texture_format(), 1); + let textures = TexturesDelta::default(); let gui = Gui::new(); Self { @@ -41,6 +45,7 @@ impl Framework { screen_descriptor, rpass, paint_jobs: Vec::new(), + textures, gui, } } @@ -67,14 +72,15 @@ impl Framework { pub(crate) fn prepare(&mut self, window: &Window) { // Run the egui frame and create all paint jobs to prepare for rendering. let raw_input = self.egui_state.take_egui_input(window); - let (output, paint_commands) = self.egui_ctx.run(raw_input, |egui_ctx| { + let output = self.egui_ctx.run(raw_input, |egui_ctx| { // Draw the demo application. self.gui.ui(egui_ctx); }); + self.textures.append(output.textures_delta); self.egui_state - .handle_output(window, &self.egui_ctx, output); - self.paint_jobs = self.egui_ctx.tessellate(paint_commands); + .handle_platform_output(window, &self.egui_ctx, output.platform_output); + self.paint_jobs = self.egui_ctx.tessellate(output.shapes); } /// Render egui. @@ -83,12 +89,12 @@ impl Framework { encoder: &mut wgpu::CommandEncoder, render_target: &wgpu::TextureView, context: &PixelsContext, - ) -> Result<(), BackendError> { + ) { // Upload all resources to the GPU. - self.rpass - .update_texture(&context.device, &context.queue, &self.egui_ctx.font_image()); - self.rpass - .update_user_textures(&context.device, &context.queue); + for (id, image_delta) in &self.textures.set { + self.rpass + .update_texture(&context.device, &context.queue, *id, image_delta); + } self.rpass.update_buffers( &context.device, &context.queue, @@ -103,7 +109,13 @@ impl Framework { &self.paint_jobs, &self.screen_descriptor, None, - ) + ); + + // Cleanup + let textures = std::mem::take(&mut self.textures); + for id in &textures.free { + self.rpass.remove_textures(id); + } } } @@ -114,7 +126,7 @@ impl Gui { } /// Create the UI using egui. - fn ui(&mut self, ctx: &CtxRef) { + fn ui(&mut self, ctx: &Context) { egui::TopBottomPanel::top("menubar_container").show(ctx, |ui| { egui::menu::bar(ui, |ui| { ui.menu_button("File", |ui| { diff --git a/examples/minimal-egui/src/main.rs b/examples/minimal-egui/src/main.rs index 9086b09d..3bba776e 100644 --- a/examples/minimal-egui/src/main.rs +++ b/examples/minimal-egui/src/main.rs @@ -94,7 +94,7 @@ fn main() -> Result<(), Error> { context.scaling_renderer.render(encoder, render_target); // Render egui - framework.render(encoder, render_target, context)?; + framework.render(encoder, render_target, context); Ok(()) }); diff --git a/examples/minimal-fltk/Cargo.toml b/examples/minimal-fltk/Cargo.toml index ecef9b5c..74feb937 100644 --- a/examples/minimal-fltk/Cargo.toml +++ b/examples/minimal-fltk/Cargo.toml @@ -10,7 +10,7 @@ optimize = ["log/release_max_level_warn"] default = ["optimize"] [dependencies] -fltk = { version = "1.2", features = ["raw-window-handle", "no-images", "no-pango"] } +fltk = { version = "1.3", features = ["raw-window-handle", "no-images", "no-pango"] } env_logger = "0.9" log = "0.4" pixels = { path = "../.." } diff --git a/examples/minimal-web/Cargo.toml b/examples/minimal-web/Cargo.toml index 18945784..f45ed2e2 100644 --- a/examples/minimal-web/Cargo.toml +++ b/examples/minimal-web/Cargo.toml @@ -13,9 +13,9 @@ default = ["optimize"] [dependencies] log = "0.4" pixels = { path = "../.." } -wgpu = "0.12" +wgpu = "0.13" winit = "0.26" -winit_input_helper = "0.11" +winit_input_helper = "0.12" [target.'cfg(target_arch = "wasm32")'.dependencies] console_error_panic_hook = "0.1" diff --git a/examples/minimal-winit/Cargo.toml b/examples/minimal-winit/Cargo.toml index 8d2b6ce1..a8692883 100644 --- a/examples/minimal-winit/Cargo.toml +++ b/examples/minimal-winit/Cargo.toml @@ -14,4 +14,4 @@ env_logger = "0.9" log = "0.4" pixels = { path = "../.." } winit = "0.26" -winit_input_helper = "0.11" +winit_input_helper = "0.12" diff --git a/examples/raqote-winit/Cargo.toml b/examples/raqote-winit/Cargo.toml index f1b66f74..c559afcb 100644 --- a/examples/raqote-winit/Cargo.toml +++ b/examples/raqote-winit/Cargo.toml @@ -15,7 +15,7 @@ euclid = "0.22" log = "0.4" pixels = { path = "../.." } winit = "0.26" -winit_input_helper = "0.11" +winit_input_helper = "0.12" [dependencies.raqote] git = "https://github.com/jrmuizel/raqote.git" diff --git a/shaders/scale.wgsl b/shaders/scale.wgsl index 166b5dfd..d977f201 100644 --- a/shaders/scale.wgsl +++ b/shaders/scale.wgsl @@ -1,18 +1,18 @@ // Vertex shader bindings struct VertexOutput { - [[location(0)]] tex_coord: vec2; - [[builtin(position)]] position: vec4; -}; + @location(0) tex_coord: vec2, + @builtin(position) position: vec4, +} struct Locals { - transform: mat4x4; -}; -[[group(0), binding(2)]] var r_locals: Locals; + transform: mat4x4, +} +@group(0) @binding(2) var r_locals: Locals; -[[stage(vertex)]] +@vertex fn vs_main( - [[location(0)]] position: vec2, + @location(0) position: vec2, ) -> VertexOutput { var out: VertexOutput; out.tex_coord = fma(position, vec2(0.5, -0.5), vec2(0.5, 0.5)); @@ -22,10 +22,10 @@ fn vs_main( // Fragment shader bindings -[[group(0), binding(0)]] var r_tex_color: texture_2d; -[[group(0), binding(1)]] var r_tex_sampler: sampler; +@group(0) @binding(0) var r_tex_color: texture_2d; +@group(0) @binding(1) var r_tex_sampler: sampler; -[[stage(fragment)]] -fn fs_main([[location(0)]] tex_coord: vec2) -> [[location(0)]] vec4 { +@fragment +fn fs_main(@location(0) tex_coord: vec2) -> @location(0) vec4 { return textureSample(r_tex_color, r_tex_sampler, tex_coord); } diff --git a/src/builder.rs b/src/builder.rs index 32932261..73db59f8 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -17,6 +17,7 @@ pub struct PixelsBuilder<'req, 'dev, 'win, W: HasRawWindowHandle> { render_texture_format: Option, surface_texture_format: Option, clear_color: wgpu::Color, + blend_state: wgpu::BlendState, } impl<'req, 'dev, 'win, W: HasRawWindowHandle> PixelsBuilder<'req, 'dev, 'win, W> { @@ -71,6 +72,7 @@ impl<'req, 'dev, 'win, W: HasRawWindowHandle> PixelsBuilder<'req, 'dev, 'win, W> render_texture_format: None, surface_texture_format: None, clear_color: wgpu::Color::BLACK, + blend_state: wgpu::BlendState::ALPHA_BLENDING, } } @@ -189,11 +191,37 @@ impl<'req, 'dev, 'win, W: HasRawWindowHandle> PixelsBuilder<'req, 'dev, 'win, W> self } - /// Set the default clear color. + /// Set the blend state. + /// + /// Allows customization of how to mix the new and existing pixels in a texture + /// when rendering. + /// + /// The default blend state is alpha blending with non-premultiplied alpha. + /// + /// ```no_run + /// use pixels::wgpu::BlendState; + /// + /// # use pixels::PixelsBuilder; + /// # let window = pixels_mocks::Rwh; + /// # let surface_texture = pixels::SurfaceTexture::new(320, 240, &window); + /// // Replace the old pixels with the new without mixing. + /// let mut pixels = PixelsBuilder::new(320, 240, surface_texture) + /// .blend_state(wgpu::BlendState::REPLACE) + /// .build()?; + /// # Ok::<(), pixels::Error>(()) + /// ``` + pub fn blend_state(mut self, blend_state: wgpu::BlendState) -> Self { + self.blend_state = blend_state; + self + } + + /// Set the clear color. /// /// Allows customization of the background color and the border drawn for non-integer scale /// values. /// + /// The default value is pure black. + /// /// ```no_run /// use pixels::wgpu::Color; /// @@ -268,15 +296,17 @@ impl<'req, 'dev, 'win, W: HasRawWindowHandle> PixelsBuilder<'req, 'dev, 'win, W> let present_mode = self.present_mode; let surface_texture_format = self.surface_texture_format.unwrap_or_else(|| { - surface - .get_preferred_format(&adapter) - .unwrap_or(wgpu::TextureFormat::Bgra8UnormSrgb) + *surface + .get_supported_formats(&adapter) + .first() + .unwrap_or(&wgpu::TextureFormat::Bgra8UnormSrgb) }); let render_texture_format = self.render_texture_format.unwrap_or(surface_texture_format); // Create the backing texture let surface_size = self.surface_texture.size; let clear_color = self.clear_color; + let blend_state = self.blend_state; let (scaling_matrix_inverse, texture_extent, texture, scaling_renderer, pixels_buffer_size) = create_backing_texture( &device, @@ -288,6 +318,7 @@ impl<'req, 'dev, 'win, W: HasRawWindowHandle> PixelsBuilder<'req, 'dev, 'win, W> &surface_size, render_texture_format, clear_color, + blend_state, ); // Create the pixel buffer @@ -312,6 +343,7 @@ impl<'req, 'dev, 'win, W: HasRawWindowHandle> PixelsBuilder<'req, 'dev, 'win, W> present_mode, render_texture_format, surface_texture_format, + blend_state, pixels, scaling_matrix_inverse, }; @@ -360,6 +392,7 @@ impl<'req, 'dev, 'win, W: HasRawWindowHandle> PixelsBuilder<'req, 'dev, 'win, W> } } +#[allow(clippy::too_many_arguments)] pub(crate) fn create_backing_texture( device: &wgpu::Device, width: u32, @@ -368,6 +401,7 @@ pub(crate) fn create_backing_texture( surface_size: &SurfaceSize, render_texture_format: wgpu::TextureFormat, clear_color: wgpu::Color, + blend_state: wgpu::BlendState, ) -> ( ultraviolet::Mat4, wgpu::Extent3d, @@ -406,6 +440,7 @@ pub(crate) fn create_backing_texture( surface_size, render_texture_format, clear_color, + blend_state, ); let texture_format_size = get_texture_format_size(backing_texture_format); @@ -497,74 +532,22 @@ const fn get_texture_format_size(texture_format: wgpu::TextureFormat) -> f32 { | EacR11Unorm | EacR11Snorm => 0.5, // 4.0 * 4.0 / 8.0 - // 4x4 blocks, 16 bytes per block - Bc2RgbaUnorm - | Bc2RgbaUnormSrgb - | Bc3RgbaUnorm - | Bc3RgbaUnormSrgb - | Bc5RgUnorm - | Bc5RgSnorm - | Bc6hRgbUfloat - | Bc6hRgbSfloat - | Bc7RgbaUnorm - | Bc7RgbaUnormSrgb - | EacRg11Unorm - | EacRg11Snorm - | Etc2Rgba8Unorm - | Etc2Rgba8UnormSrgb - | Astc4x4RgbaUnorm - | Astc4x4RgbaUnormSrgb => 1.0, // 4.0 * 4.0 / 16.0 - - // 5x4 blocks, 16 bytes per block - Astc5x4RgbaUnorm - | Astc5x4RgbaUnormSrgb => 1.25, // 5.0 * 4.0 / 16.0 - - // 5x5 blocks, 16 bytes per block - Astc5x5RgbaUnorm - | Astc5x5RgbaUnormSrgb => 1.5625, // 5.0 * 5.0 / 16.0 - - // 6x5 blocks, 16 bytes per block - Astc6x5RgbaUnorm - | Astc6x5RgbaUnormSrgb => 1.875, // 6.0 * 5.0 / 16.0 - - // 6x6 blocks, 16 bytes per block - Astc6x6RgbaUnorm - | Astc6x6RgbaUnormSrgb => 2.25, // 6.0 * 6.0 / 16.0 - - // 8x5 blocks, 16 bytes per block - Astc8x5RgbaUnorm - | Astc8x5RgbaUnormSrgb => 2.5, // 8.0 * 5.0 / 16.0 - - // 8x6 blocks, 16 bytes per block - Astc8x6RgbaUnorm - | Astc8x6RgbaUnormSrgb => 3.0, // 8.0 * 6.0 / 16.0 - - // 8x8 blocks, 16 bytes per block - Astc8x8RgbaUnorm - | Astc8x8RgbaUnormSrgb => 4.0, // 8.0 * 8.0 / 16.0 - - // 10x5 blocks, 16 bytes per block - Astc10x5RgbaUnorm - | Astc10x5RgbaUnormSrgb => 3.125, // 10.0 * 5.0 / 16.0 - - // 10x6 blocks, 16 bytes per block - Astc10x6RgbaUnorm - | Astc10x6RgbaUnormSrgb => 3.75, // 10.0 * 6.0 / 16.0 - - // 10x8 blocks, 16 bytes per block - Astc10x8RgbaUnorm - | Astc10x8RgbaUnormSrgb => 5.0, // 10.0 * 8.0 / 16.0 - - // 10x10 blocks, 16 bytes per block - Astc10x10RgbaUnorm - | Astc10x10RgbaUnormSrgb => 6.25, // 10.0 * 10.0 / 16.0 - - // 12x10 blocks, 16 bytes per block - Astc12x10RgbaUnorm - | Astc12x10RgbaUnormSrgb => 7.5, // 12.0 * 10.0 / 16.0 - - // 12x12 blocks, 16 bytes per block - Astc12x12RgbaUnorm - | Astc12x12RgbaUnormSrgb => 9.0, // 12.0 * 12.0 / 16.0 + Depth32FloatStencil8 => todo!(), + Depth24UnormStencil8 => todo!(), + Bc2RgbaUnorm => todo!(), + Bc2RgbaUnormSrgb => todo!(), + Bc3RgbaUnorm => todo!(), + Bc3RgbaUnormSrgb => todo!(), + Bc5RgUnorm => todo!(), + Bc5RgSnorm => todo!(), + Bc6hRgbUfloat => todo!(), + Bc6hRgbSfloat => todo!(), + Bc7RgbaUnorm => todo!(), + Bc7RgbaUnormSrgb => todo!(), + Etc2Rgba8Unorm => todo!(), + Etc2Rgba8UnormSrgb => todo!(), + EacRg11Unorm => todo!(), + EacRg11Snorm => todo!(), + Astc { block, channel } => todo!(), } } diff --git a/src/lib.rs b/src/lib.rs index 926b3b2a..0b436507 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,6 +98,7 @@ pub struct Pixels { present_mode: wgpu::PresentMode, render_texture_format: wgpu::TextureFormat, surface_texture_format: wgpu::TextureFormat, + blend_state: wgpu::BlendState, // Pixel buffer pixels: Vec, @@ -285,6 +286,7 @@ impl Pixels { &self.surface_size, self.render_texture_format, self.context.scaling_renderer.clear_color, + self.blend_state, ); self.scaling_matrix_inverse = scaling_matrix_inverse; diff --git a/src/renderers.rs b/src/renderers.rs index 44cd70cc..73c6e0ac 100644 --- a/src/renderers.rs +++ b/src/renderers.rs @@ -23,9 +23,10 @@ impl ScalingRenderer { surface_size: &SurfaceSize, render_texture_format: wgpu::TextureFormat, clear_color: wgpu::Color, + blend_state: wgpu::BlendState, ) -> Self { let shader = wgpu::include_wgsl!("../shaders/scale.wgsl"); - let module = device.create_shader_module(&shader); + let module = device.create_shader_module(shader); // Create a texture sampler with nearest neighbor let sampler = device.create_sampler(&wgpu::SamplerDescriptor { @@ -150,11 +151,11 @@ impl ScalingRenderer { fragment: Some(wgpu::FragmentState { module: &module, entry_point: "fs_main", - targets: &[wgpu::ColorTargetState { + targets: &[Some(wgpu::ColorTargetState { format: render_texture_format, - blend: Some(wgpu::BlendState::ALPHA_BLENDING), + blend: Some(blend_state), write_mask: wgpu::ColorWrites::ALL, - }], + })], }), multiview: None, }); @@ -178,14 +179,14 @@ impl ScalingRenderer { pub fn render(&self, encoder: &mut wgpu::CommandEncoder, render_target: &wgpu::TextureView) { let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("pixels_scaling_renderer_render_pass"), - color_attachments: &[wgpu::RenderPassColorAttachment { + color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: render_target, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(self.clear_color), store: true, }, - }], + })], depth_stencil_attachment: None, }); rpass.set_pipeline(&self.render_pipeline);