mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
refactor: gpu webgl fallback and batched command encoding
Made-with: Cursor
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
use gpu::{FULLSCREEN_SHADER_SOURCE, GPU_TEXTURE_FORMAT, GpuContext};
|
use gpu::{FULLSCREEN_SHADER_SOURCE, GpuContext};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use wgpu::util::DeviceExt;
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ impl EffectPipeline {
|
|||||||
module: &gaussian_blur_shader_module,
|
module: &gaussian_blur_shader_module,
|
||||||
entry_point: Some("fragment_main"),
|
entry_point: Some("fragment_main"),
|
||||||
targets: &[Some(wgpu::ColorTargetState {
|
targets: &[Some(wgpu::ColorTargetState {
|
||||||
format: GPU_TEXTURE_FORMAT,
|
format: context.texture_format(),
|
||||||
blend: None,
|
blend: None,
|
||||||
write_mask: wgpu::ColorWrites::ALL,
|
write_mask: wgpu::ColorWrites::ALL,
|
||||||
})],
|
})],
|
||||||
@@ -149,6 +149,37 @@ impl EffectPipeline {
|
|||||||
height,
|
height,
|
||||||
passes,
|
passes,
|
||||||
}: ApplyEffectsOptions<'_>,
|
}: ApplyEffectsOptions<'_>,
|
||||||
|
) -> Result<wgpu::Texture, EffectsError> {
|
||||||
|
let mut encoder =
|
||||||
|
context
|
||||||
|
.device()
|
||||||
|
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||||
|
label: Some("effects-command-encoder"),
|
||||||
|
});
|
||||||
|
let output = self.apply_with_encoder(
|
||||||
|
context,
|
||||||
|
&mut encoder,
|
||||||
|
ApplyEffectsOptions {
|
||||||
|
source,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
passes,
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
context.queue().submit([encoder.finish()]);
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_with_encoder(
|
||||||
|
&self,
|
||||||
|
context: &GpuContext,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
ApplyEffectsOptions {
|
||||||
|
source,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
passes,
|
||||||
|
}: ApplyEffectsOptions<'_>,
|
||||||
) -> Result<wgpu::Texture, EffectsError> {
|
) -> Result<wgpu::Texture, EffectsError> {
|
||||||
let mut current_texture: Option<wgpu::Texture> = None;
|
let mut current_texture: Option<wgpu::Texture> = None;
|
||||||
|
|
||||||
@@ -199,12 +230,6 @@ impl EffectPipeline {
|
|||||||
shader: pass.shader.clone(),
|
shader: pass.shader.clone(),
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
let mut encoder =
|
|
||||||
context
|
|
||||||
.device()
|
|
||||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
|
||||||
label: Some("effects-command-encoder"),
|
|
||||||
});
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
@@ -230,7 +255,6 @@ impl EffectPipeline {
|
|||||||
render_pass.draw(0..6, 0..1);
|
render_pass.draw(0..6, 0..1);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.queue().submit([encoder.finish()]);
|
|
||||||
current_texture = Some(output_texture);
|
current_texture = Some(output_texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ bytemuck = { version = "1.25.0", features = ["derive"] }
|
|||||||
thiserror = "2.0.18"
|
thiserror = "2.0.18"
|
||||||
wgpu = "29.0.1"
|
wgpu = "29.0.1"
|
||||||
|
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
wasm-bindgen = "0.2.116"
|
||||||
|
web-sys = { version = "0.3.93", features = ["Document", "Window", "HtmlCanvasElement", "OffscreenCanvasRenderingContext2d", "ImageData"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
wasm = []
|
wasm = ["wgpu/webgl"]
|
||||||
|
|||||||
+325
-88
@@ -1,6 +1,21 @@
|
|||||||
use wgpu::util::DeviceExt;
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
use crate::{FULLSCREEN_SHADER_SOURCE, GPU_TEXTURE_FORMAT, GpuError};
|
#[cfg(all(feature = "wasm", target_arch = "wasm32"))]
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
|
||||||
|
use crate::{FULLSCREEN_SHADER_SOURCE, GpuError};
|
||||||
|
|
||||||
|
#[cfg(all(feature = "wasm", target_arch = "wasm32"))]
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct WebDisplay;
|
||||||
|
|
||||||
|
#[cfg(all(feature = "wasm", target_arch = "wasm32"))]
|
||||||
|
impl wgpu::rwh::HasDisplayHandle for WebDisplay {
|
||||||
|
fn display_handle(&self) -> Result<wgpu::rwh::DisplayHandle<'_>, wgpu::rwh::HandleError> {
|
||||||
|
let raw = wgpu::rwh::WebDisplayHandle::new();
|
||||||
|
Ok(unsafe { wgpu::rwh::DisplayHandle::borrow_raw(raw.into()) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const BLIT_SHADER_SOURCE: &str = include_str!("shaders/blit.wgsl");
|
const BLIT_SHADER_SOURCE: &str = include_str!("shaders/blit.wgsl");
|
||||||
|
|
||||||
@@ -18,35 +33,23 @@ pub struct GpuContext {
|
|||||||
adapter: wgpu::Adapter,
|
adapter: wgpu::Adapter,
|
||||||
device: wgpu::Device,
|
device: wgpu::Device,
|
||||||
queue: wgpu::Queue,
|
queue: wgpu::Queue,
|
||||||
|
texture_format: wgpu::TextureFormat,
|
||||||
fullscreen_quad: wgpu::Buffer,
|
fullscreen_quad: wgpu::Buffer,
|
||||||
linear_sampler: wgpu::Sampler,
|
linear_sampler: wgpu::Sampler,
|
||||||
nearest_sampler: wgpu::Sampler,
|
nearest_sampler: wgpu::Sampler,
|
||||||
texture_sampler_bind_group_layout: wgpu::BindGroupLayout,
|
texture_sampler_bind_group_layout: wgpu::BindGroupLayout,
|
||||||
blit_pipeline: wgpu::RenderPipeline,
|
blit_pipeline: wgpu::RenderPipeline,
|
||||||
|
supports_external_texture_copies: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GpuContext {
|
impl GpuContext {
|
||||||
pub async fn new() -> Result<Self, GpuError> {
|
pub async fn new() -> Result<Self, GpuError> {
|
||||||
let instance = wgpu::Instance::default();
|
let (instance, adapter, device, queue) = Self::acquire_device().await?;
|
||||||
let adapter = instance
|
let texture_format = if adapter.get_info().backend == wgpu::Backend::Gl {
|
||||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
wgpu::TextureFormat::Rgba8Unorm
|
||||||
power_preference: wgpu::PowerPreference::HighPerformance,
|
} else {
|
||||||
compatible_surface: None,
|
wgpu::TextureFormat::Bgra8Unorm
|
||||||
force_fallback_adapter: false,
|
};
|
||||||
})
|
|
||||||
.await
|
|
||||||
.map_err(|_| GpuError::AdapterUnavailable)?;
|
|
||||||
let (device, queue) = adapter
|
|
||||||
.request_device(&wgpu::DeviceDescriptor {
|
|
||||||
label: Some("gpu-device"),
|
|
||||||
required_features: wgpu::Features::empty(),
|
|
||||||
required_limits: wgpu::Limits::downlevel_webgl2_defaults()
|
|
||||||
.using_resolution(adapter.limits()),
|
|
||||||
memory_hints: wgpu::MemoryHints::Performance,
|
|
||||||
experimental_features: wgpu::ExperimentalFeatures::disabled(),
|
|
||||||
trace: wgpu::Trace::Off,
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
let fullscreen_quad = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
let fullscreen_quad = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
label: Some("gpu-fullscreen-quad-buffer"),
|
label: Some("gpu-fullscreen-quad-buffer"),
|
||||||
contents: bytemuck::cast_slice(&FULLSCREEN_QUAD_POSITIONS),
|
contents: bytemuck::cast_slice(&FULLSCREEN_QUAD_POSITIONS),
|
||||||
@@ -128,7 +131,7 @@ impl GpuContext {
|
|||||||
module: &blit_shader_module,
|
module: &blit_shader_module,
|
||||||
entry_point: Some("fragment_main"),
|
entry_point: Some("fragment_main"),
|
||||||
targets: &[Some(wgpu::ColorTargetState {
|
targets: &[Some(wgpu::ColorTargetState {
|
||||||
format: GPU_TEXTURE_FORMAT,
|
format: texture_format,
|
||||||
blend: None,
|
blend: None,
|
||||||
write_mask: wgpu::ColorWrites::ALL,
|
write_mask: wgpu::ColorWrites::ALL,
|
||||||
})],
|
})],
|
||||||
@@ -141,19 +144,99 @@ impl GpuContext {
|
|||||||
cache: None,
|
cache: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let supports_external_texture_copies = adapter
|
||||||
|
.get_downlevel_capabilities()
|
||||||
|
.flags
|
||||||
|
.contains(wgpu::DownlevelFlags::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
instance,
|
instance,
|
||||||
adapter,
|
adapter,
|
||||||
device,
|
device,
|
||||||
queue,
|
queue,
|
||||||
|
texture_format,
|
||||||
fullscreen_quad,
|
fullscreen_quad,
|
||||||
linear_sampler,
|
linear_sampler,
|
||||||
nearest_sampler,
|
nearest_sampler,
|
||||||
texture_sampler_bind_group_layout,
|
texture_sampler_bind_group_layout,
|
||||||
blit_pipeline,
|
blit_pipeline,
|
||||||
|
supports_external_texture_copies,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn acquire_device()
|
||||||
|
-> Result<(wgpu::Instance, wgpu::Adapter, wgpu::Device, wgpu::Queue), GpuError> {
|
||||||
|
let instance = wgpu::util::new_instance_with_webgpu_detection(
|
||||||
|
wgpu::InstanceDescriptor::new_without_display_handle(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match Self::try_request_device(&instance, None).await {
|
||||||
|
Ok((adapter, device, queue)) => return Ok((instance, adapter, device, queue)),
|
||||||
|
Err(_) => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::try_gl_fallback().await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "wasm", target_arch = "wasm32"))]
|
||||||
|
async fn try_gl_fallback()
|
||||||
|
-> Result<(wgpu::Instance, wgpu::Adapter, wgpu::Device, wgpu::Queue), GpuError> {
|
||||||
|
let mut gl_desc = wgpu::InstanceDescriptor::new_without_display_handle();
|
||||||
|
gl_desc.backends = wgpu::Backends::GL;
|
||||||
|
gl_desc.display = Some(Box::new(WebDisplay));
|
||||||
|
let gl_instance = wgpu::Instance::new(gl_desc);
|
||||||
|
|
||||||
|
let document = web_sys::window()
|
||||||
|
.and_then(|w| w.document())
|
||||||
|
.ok_or(GpuError::AdapterUnavailable)?;
|
||||||
|
let canvas: web_sys::HtmlCanvasElement = document
|
||||||
|
.create_element("canvas")
|
||||||
|
.map_err(|_| GpuError::AdapterUnavailable)?
|
||||||
|
.unchecked_into();
|
||||||
|
canvas.set_width(1);
|
||||||
|
canvas.set_height(1);
|
||||||
|
let surface = gl_instance.create_surface(wgpu::SurfaceTarget::Canvas(canvas))?;
|
||||||
|
|
||||||
|
let (adapter, device, queue) =
|
||||||
|
Self::try_request_device(&gl_instance, Some(&surface)).await?;
|
||||||
|
Ok((gl_instance, adapter, device, queue))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(all(feature = "wasm", target_arch = "wasm32")))]
|
||||||
|
async fn try_gl_fallback()
|
||||||
|
-> Result<(wgpu::Instance, wgpu::Adapter, wgpu::Device, wgpu::Queue), GpuError> {
|
||||||
|
Err(GpuError::AdapterUnavailable)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn try_request_device(
|
||||||
|
instance: &wgpu::Instance,
|
||||||
|
compatible_surface: Option<&wgpu::Surface<'_>>,
|
||||||
|
) -> Result<(wgpu::Adapter, wgpu::Device, wgpu::Queue), GpuError> {
|
||||||
|
let adapter = instance
|
||||||
|
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||||
|
power_preference: wgpu::PowerPreference::HighPerformance,
|
||||||
|
compatible_surface,
|
||||||
|
force_fallback_adapter: false,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|_| GpuError::AdapterUnavailable)?;
|
||||||
|
|
||||||
|
let (device, queue) = adapter
|
||||||
|
.request_device(&wgpu::DeviceDescriptor {
|
||||||
|
label: Some("gpu-device"),
|
||||||
|
required_features: wgpu::Features::empty(),
|
||||||
|
required_limits: wgpu::Limits::downlevel_webgl2_defaults()
|
||||||
|
.using_resolution(adapter.limits()),
|
||||||
|
memory_hints: wgpu::MemoryHints::Performance,
|
||||||
|
experimental_features: wgpu::ExperimentalFeatures::disabled(),
|
||||||
|
trace: wgpu::Trace::Off,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok((adapter, device, queue))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_render_texture(
|
pub fn create_render_texture(
|
||||||
&self,
|
&self,
|
||||||
width: u32,
|
width: u32,
|
||||||
@@ -170,7 +253,7 @@ impl GpuContext {
|
|||||||
mip_level_count: 1,
|
mip_level_count: 1,
|
||||||
sample_count: 1,
|
sample_count: 1,
|
||||||
dimension: wgpu::TextureDimension::D2,
|
dimension: wgpu::TextureDimension::D2,
|
||||||
format: GPU_TEXTURE_FORMAT,
|
format: self.texture_format,
|
||||||
usage: wgpu::TextureUsages::TEXTURE_BINDING
|
usage: wgpu::TextureUsages::TEXTURE_BINDING
|
||||||
| wgpu::TextureUsages::COPY_DST
|
| wgpu::TextureUsages::COPY_DST
|
||||||
| wgpu::TextureUsages::COPY_SRC
|
| wgpu::TextureUsages::COPY_SRC
|
||||||
@@ -195,6 +278,10 @@ impl GpuContext {
|
|||||||
&self.queue
|
&self.queue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn texture_format(&self) -> wgpu::TextureFormat {
|
||||||
|
self.texture_format
|
||||||
|
}
|
||||||
|
|
||||||
pub fn fullscreen_quad(&self) -> &wgpu::Buffer {
|
pub fn fullscreen_quad(&self) -> &wgpu::Buffer {
|
||||||
&self.fullscreen_quad
|
&self.fullscreen_quad
|
||||||
}
|
}
|
||||||
@@ -211,36 +298,72 @@ impl GpuContext {
|
|||||||
&self.texture_sampler_bind_group_layout
|
&self.texture_sampler_bind_group_layout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn blit_pipeline(&self) -> &wgpu::RenderPipeline {
|
||||||
|
&self.blit_pipeline
|
||||||
|
}
|
||||||
|
|
||||||
pub fn render_texture_to_surface(
|
pub fn render_texture_to_surface(
|
||||||
&self,
|
&self,
|
||||||
texture: &wgpu::Texture,
|
texture: &wgpu::Texture,
|
||||||
surface: &wgpu::Surface<'_>,
|
surface: &wgpu::Surface<'_>,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
|
) -> Result<(), GpuError> {
|
||||||
|
self.configure_surface(surface, width, height)?;
|
||||||
|
let surface_texture = self.acquire_surface_texture(surface)?;
|
||||||
|
let target_view = surface_texture
|
||||||
|
.texture
|
||||||
|
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
let mut encoder = self
|
||||||
|
.device
|
||||||
|
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||||
|
label: Some("gpu-surface-blit-encoder"),
|
||||||
|
});
|
||||||
|
self.encode_texture_blit_to_view(&mut encoder, texture, &target_view, "gpu-surface-blit");
|
||||||
|
self.queue.submit([encoder.finish()]);
|
||||||
|
surface_texture.present();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn configure_surface(
|
||||||
|
&self,
|
||||||
|
surface: &wgpu::Surface<'_>,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
) -> Result<(), GpuError> {
|
) -> Result<(), GpuError> {
|
||||||
let Some(config) = surface.get_default_config(&self.adapter, width, height) else {
|
let Some(config) = surface.get_default_config(&self.adapter, width, height) else {
|
||||||
return Err(GpuError::UnsupportedSurfaceFormat);
|
return Err(GpuError::UnsupportedSurfaceFormat);
|
||||||
};
|
};
|
||||||
if config.format != GPU_TEXTURE_FORMAT {
|
if config.format != self.texture_format {
|
||||||
return Err(GpuError::UnsupportedSurfaceFormat);
|
return Err(GpuError::UnsupportedSurfaceFormat);
|
||||||
}
|
}
|
||||||
surface.configure(&self.device, &config);
|
surface.configure(&self.device, &config);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
let surface_texture = match surface.get_current_texture() {
|
pub fn acquire_surface_texture(
|
||||||
|
&self,
|
||||||
|
surface: &wgpu::Surface<'_>,
|
||||||
|
) -> Result<wgpu::SurfaceTexture, GpuError> {
|
||||||
|
match surface.get_current_texture() {
|
||||||
wgpu::CurrentSurfaceTexture::Success(surface_texture)
|
wgpu::CurrentSurfaceTexture::Success(surface_texture)
|
||||||
| wgpu::CurrentSurfaceTexture::Suboptimal(surface_texture) => surface_texture,
|
| wgpu::CurrentSurfaceTexture::Suboptimal(surface_texture) => Ok(surface_texture),
|
||||||
wgpu::CurrentSurfaceTexture::Timeout
|
wgpu::CurrentSurfaceTexture::Timeout
|
||||||
| wgpu::CurrentSurfaceTexture::Occluded
|
| wgpu::CurrentSurfaceTexture::Occluded
|
||||||
| wgpu::CurrentSurfaceTexture::Outdated
|
| wgpu::CurrentSurfaceTexture::Outdated
|
||||||
| wgpu::CurrentSurfaceTexture::Lost
|
| wgpu::CurrentSurfaceTexture::Lost
|
||||||
| wgpu::CurrentSurfaceTexture::Validation => {
|
| wgpu::CurrentSurfaceTexture::Validation => Err(GpuError::UnsupportedSurfaceFormat),
|
||||||
return Err(GpuError::UnsupportedSurfaceFormat);
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
pub fn encode_texture_blit_to_view(
|
||||||
|
&self,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
texture: &wgpu::Texture,
|
||||||
|
target_view: &wgpu::TextureView,
|
||||||
|
label: &'static str,
|
||||||
|
) {
|
||||||
let source_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
let source_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
let target_view = surface_texture
|
|
||||||
.texture
|
|
||||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
|
||||||
let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
label: Some("gpu-blit-bind-group"),
|
label: Some("gpu-blit-bind-group"),
|
||||||
layout: &self.texture_sampler_bind_group_layout,
|
layout: &self.texture_sampler_bind_group_layout,
|
||||||
@@ -255,38 +378,27 @@ impl GpuContext {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
let mut encoder = self
|
|
||||||
.device
|
|
||||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
|
||||||
label: Some("gpu-surface-blit-encoder"),
|
|
||||||
});
|
|
||||||
|
|
||||||
{
|
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
label: Some(label),
|
||||||
label: Some("gpu-surface-blit-pass"),
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
view: target_view,
|
||||||
view: &target_view,
|
resolve_target: None,
|
||||||
resolve_target: None,
|
depth_slice: None,
|
||||||
depth_slice: None,
|
ops: wgpu::Operations {
|
||||||
ops: wgpu::Operations {
|
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
||||||
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
store: wgpu::StoreOp::Store,
|
||||||
store: wgpu::StoreOp::Store,
|
},
|
||||||
},
|
})],
|
||||||
})],
|
depth_stencil_attachment: None,
|
||||||
depth_stencil_attachment: None,
|
occlusion_query_set: None,
|
||||||
occlusion_query_set: None,
|
timestamp_writes: None,
|
||||||
timestamp_writes: None,
|
multiview_mask: None,
|
||||||
multiview_mask: None,
|
});
|
||||||
});
|
render_pass.set_pipeline(&self.blit_pipeline);
|
||||||
render_pass.set_pipeline(&self.blit_pipeline);
|
render_pass.set_vertex_buffer(0, self.fullscreen_quad.slice(..));
|
||||||
render_pass.set_vertex_buffer(0, self.fullscreen_quad.slice(..));
|
render_pass.set_bind_group(0, &bind_group, &[]);
|
||||||
render_pass.set_bind_group(0, &bind_group, &[]);
|
render_pass.draw(0..6, 0..1);
|
||||||
render_pass.draw(0..6, 0..1);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.queue.submit([encoder.finish()]);
|
|
||||||
surface_texture.present();
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "wasm", target_arch = "wasm32"))]
|
#[cfg(all(feature = "wasm", target_arch = "wasm32"))]
|
||||||
@@ -298,26 +410,71 @@ impl GpuContext {
|
|||||||
label: &'static str,
|
label: &'static str,
|
||||||
) -> wgpu::Texture {
|
) -> wgpu::Texture {
|
||||||
let texture = self.create_render_texture(width, height, label);
|
let texture = self.create_render_texture(width, height, label);
|
||||||
self.queue.copy_external_image_to_texture(
|
|
||||||
&wgpu::CopyExternalImageSourceInfo {
|
if self.supports_external_texture_copies {
|
||||||
source: wgpu::ExternalImageSource::OffscreenCanvas(canvas.clone()),
|
self.queue.copy_external_image_to_texture(
|
||||||
origin: wgpu::Origin2d::ZERO,
|
&wgpu::CopyExternalImageSourceInfo {
|
||||||
flip_y: true,
|
source: wgpu::ExternalImageSource::OffscreenCanvas(canvas.clone()),
|
||||||
},
|
origin: wgpu::Origin2d::ZERO,
|
||||||
wgpu::CopyExternalImageDestInfo {
|
flip_y: false,
|
||||||
texture: &texture,
|
},
|
||||||
mip_level: 0,
|
wgpu::CopyExternalImageDestInfo {
|
||||||
origin: wgpu::Origin3d::ZERO,
|
texture: &texture,
|
||||||
aspect: wgpu::TextureAspect::All,
|
mip_level: 0,
|
||||||
color_space: wgpu::PredefinedColorSpace::Srgb,
|
origin: wgpu::Origin3d::ZERO,
|
||||||
premultiplied_alpha: false,
|
aspect: wgpu::TextureAspect::All,
|
||||||
},
|
color_space: wgpu::PredefinedColorSpace::Srgb,
|
||||||
wgpu::Extent3d {
|
premultiplied_alpha: false,
|
||||||
width,
|
},
|
||||||
height,
|
wgpu::Extent3d {
|
||||||
depth_or_array_layers: 1,
|
width,
|
||||||
},
|
height,
|
||||||
);
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let ctx: web_sys::OffscreenCanvasRenderingContext2d = canvas
|
||||||
|
.get_context("2d")
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.expect("Failed to get 2d context for texture import")
|
||||||
|
.unchecked_into();
|
||||||
|
let image_data = ctx
|
||||||
|
.get_image_data(0.0, 0.0, width as f64, height as f64)
|
||||||
|
.expect("Failed to read pixel data from canvas");
|
||||||
|
let rgba_bytes = image_data.data();
|
||||||
|
|
||||||
|
let pixel_bytes = if self.texture_format == wgpu::TextureFormat::Bgra8Unorm {
|
||||||
|
let mut bytes = rgba_bytes.to_vec();
|
||||||
|
for pixel in bytes.chunks_exact_mut(4) {
|
||||||
|
pixel.swap(0, 2);
|
||||||
|
}
|
||||||
|
bytes
|
||||||
|
} else {
|
||||||
|
rgba_bytes.to_vec()
|
||||||
|
};
|
||||||
|
|
||||||
|
self.queue.write_texture(
|
||||||
|
wgpu::TexelCopyTextureInfo {
|
||||||
|
texture: &texture,
|
||||||
|
mip_level: 0,
|
||||||
|
origin: wgpu::Origin3d::ZERO,
|
||||||
|
aspect: wgpu::TextureAspect::All,
|
||||||
|
},
|
||||||
|
&pixel_bytes,
|
||||||
|
wgpu::TexelCopyBufferLayout {
|
||||||
|
offset: 0,
|
||||||
|
bytes_per_row: Some(width * 4),
|
||||||
|
rows_per_image: Some(height),
|
||||||
|
},
|
||||||
|
wgpu::Extent3d {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
texture
|
texture
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,9 +486,89 @@ impl GpuContext {
|
|||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
) -> Result<(), GpuError> {
|
) -> Result<(), GpuError> {
|
||||||
let surface = self
|
if self.supports_external_texture_copies {
|
||||||
.instance
|
let surface = self
|
||||||
.create_surface(wgpu::SurfaceTarget::OffscreenCanvas(canvas.clone()))?;
|
.instance
|
||||||
self.render_texture_to_surface(texture, &surface, width, height)
|
.create_surface(wgpu::SurfaceTarget::OffscreenCanvas(canvas.clone()))?;
|
||||||
|
return self.render_texture_to_surface(texture, &surface, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.readback_texture_to_offscreen_canvas(texture, canvas, width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "wasm", target_arch = "wasm32"))]
|
||||||
|
fn readback_texture_to_offscreen_canvas(
|
||||||
|
&self,
|
||||||
|
texture: &wgpu::Texture,
|
||||||
|
canvas: &wgpu::web_sys::OffscreenCanvas,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(), GpuError> {
|
||||||
|
let buffer_size = (width * height * 4) as u64;
|
||||||
|
let buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some("gpu-readback-buffer"),
|
||||||
|
size: buffer_size,
|
||||||
|
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut encoder = self
|
||||||
|
.device
|
||||||
|
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||||
|
label: Some("gpu-readback-encoder"),
|
||||||
|
});
|
||||||
|
encoder.copy_texture_to_buffer(
|
||||||
|
wgpu::TexelCopyTextureInfo {
|
||||||
|
texture,
|
||||||
|
mip_level: 0,
|
||||||
|
origin: wgpu::Origin3d::ZERO,
|
||||||
|
aspect: wgpu::TextureAspect::All,
|
||||||
|
},
|
||||||
|
wgpu::TexelCopyBufferInfo {
|
||||||
|
buffer: &buffer,
|
||||||
|
layout: wgpu::TexelCopyBufferLayout {
|
||||||
|
offset: 0,
|
||||||
|
bytes_per_row: Some(width * 4),
|
||||||
|
rows_per_image: Some(height),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wgpu::Extent3d {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self.queue.submit([encoder.finish()]);
|
||||||
|
|
||||||
|
let slice = buffer.slice(..);
|
||||||
|
slice.map_async(wgpu::MapMode::Read, |_| {});
|
||||||
|
let _ = self.device.poll(wgpu::PollType::wait_indefinitely());
|
||||||
|
|
||||||
|
let data = slice.get_mapped_range();
|
||||||
|
let mut rgba_bytes = data.to_vec();
|
||||||
|
drop(data);
|
||||||
|
buffer.unmap();
|
||||||
|
|
||||||
|
if self.texture_format == wgpu::TextureFormat::Bgra8Unorm {
|
||||||
|
for pixel in rgba_bytes.chunks_exact_mut(4) {
|
||||||
|
pixel.swap(0, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let clamped = wasm_bindgen::Clamped(&rgba_bytes[..]);
|
||||||
|
let image_data =
|
||||||
|
web_sys::ImageData::new_with_u8_clamped_array_and_sh(clamped, width, height)
|
||||||
|
.map_err(|_| GpuError::AdapterUnavailable)?;
|
||||||
|
|
||||||
|
let ctx: web_sys::OffscreenCanvasRenderingContext2d = canvas
|
||||||
|
.get_context("2d")
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.ok_or(GpuError::AdapterUnavailable)?
|
||||||
|
.unchecked_into();
|
||||||
|
ctx.put_image_data(&image_data, 0.0, 0.0)
|
||||||
|
.map_err(|_| GpuError::AdapterUnavailable)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ struct VertexOutput {
|
|||||||
fn vertex_main(@location(0) position: vec2f) -> VertexOutput {
|
fn vertex_main(@location(0) position: vec2f) -> VertexOutput {
|
||||||
var output: VertexOutput;
|
var output: VertexOutput;
|
||||||
output.position = vec4f(position, 0.0, 1.0);
|
output.position = vec4f(position, 0.0, 1.0);
|
||||||
output.tex_coord = (position * 0.5) + vec2f(0.5, 0.5);
|
output.tex_coord = vec2f(position.x * 0.5 + 0.5, 0.5 - position.y * 0.5);
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
use gpu::{FULLSCREEN_SHADER_SOURCE, GPU_TEXTURE_FORMAT, GpuContext};
|
use gpu::{FULLSCREEN_SHADER_SOURCE, GpuContext};
|
||||||
use wgpu::util::DeviceExt;
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
use crate::SdfPipeline;
|
use crate::SdfPipeline;
|
||||||
@@ -128,7 +128,7 @@ impl MaskFeatherPipeline {
|
|||||||
module: &fragment_shader_module,
|
module: &fragment_shader_module,
|
||||||
entry_point: Some("fragment_main"),
|
entry_point: Some("fragment_main"),
|
||||||
targets: &[Some(wgpu::ColorTargetState {
|
targets: &[Some(wgpu::ColorTargetState {
|
||||||
format: GPU_TEXTURE_FORMAT,
|
format: context.texture_format(),
|
||||||
blend: None,
|
blend: None,
|
||||||
write_mask: wgpu::ColorWrites::ALL,
|
write_mask: wgpu::ColorWrites::ALL,
|
||||||
})],
|
})],
|
||||||
@@ -159,10 +159,41 @@ impl MaskFeatherPipeline {
|
|||||||
height,
|
height,
|
||||||
feather,
|
feather,
|
||||||
}: ApplyMaskFeatherOptions<'_>,
|
}: ApplyMaskFeatherOptions<'_>,
|
||||||
|
) -> wgpu::Texture {
|
||||||
|
let mut encoder =
|
||||||
|
context
|
||||||
|
.device()
|
||||||
|
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||||
|
label: Some("gpu-mask-distance-command-encoder"),
|
||||||
|
});
|
||||||
|
let output = self.apply_mask_feather_with_encoder(
|
||||||
|
context,
|
||||||
|
&mut encoder,
|
||||||
|
ApplyMaskFeatherOptions {
|
||||||
|
mask,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
feather,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
context.queue().submit([encoder.finish()]);
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_mask_feather_with_encoder(
|
||||||
|
&self,
|
||||||
|
context: &GpuContext,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
ApplyMaskFeatherOptions {
|
||||||
|
mask,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
feather,
|
||||||
|
}: ApplyMaskFeatherOptions<'_>,
|
||||||
) -> wgpu::Texture {
|
) -> wgpu::Texture {
|
||||||
let sdf = self
|
let sdf = self
|
||||||
.sdf_pipeline
|
.sdf_pipeline
|
||||||
.compute_signed_distance_field(context, mask, width, height);
|
.compute_signed_distance_field_with_encoder(context, encoder, mask, width, height);
|
||||||
let output_texture = context.create_render_texture(width, height, "masks-feather-output");
|
let output_texture = context.create_render_texture(width, height, "masks-feather-output");
|
||||||
let inside_view = sdf
|
let inside_view = sdf
|
||||||
.inside_texture
|
.inside_texture
|
||||||
@@ -225,13 +256,6 @@ impl MaskFeatherPipeline {
|
|||||||
resource: uniform_buffer.as_entire_binding(),
|
resource: uniform_buffer.as_entire_binding(),
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
let mut encoder =
|
|
||||||
context
|
|
||||||
.device()
|
|
||||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
|
||||||
label: Some("gpu-mask-distance-command-encoder"),
|
|
||||||
});
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
label: Some("gpu-mask-distance-render-pass"),
|
label: Some("gpu-mask-distance-render-pass"),
|
||||||
@@ -256,8 +280,6 @@ impl MaskFeatherPipeline {
|
|||||||
render_pass.set_bind_group(2, &uniform_bind_group, &[]);
|
render_pass.set_bind_group(2, &uniform_bind_group, &[]);
|
||||||
render_pass.draw(0..6, 0..1);
|
render_pass.draw(0..6, 0..1);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.queue().submit([encoder.finish()]);
|
|
||||||
output_texture
|
output_texture
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
use gpu::{FULLSCREEN_SHADER_SOURCE, GPU_TEXTURE_FORMAT, GpuContext};
|
use gpu::{FULLSCREEN_SHADER_SOURCE, GpuContext};
|
||||||
use wgpu::util::DeviceExt;
|
use wgpu::util::DeviceExt;
|
||||||
|
|
||||||
const JFA_INIT_SHADER_SOURCE: &str = include_str!("shaders/jfa_init.wgsl");
|
const JFA_INIT_SHADER_SOURCE: &str = include_str!("shaders/jfa_init.wgsl");
|
||||||
@@ -98,6 +98,7 @@ impl SdfPipeline {
|
|||||||
&pipeline_layout,
|
&pipeline_layout,
|
||||||
&vertex_shader_module,
|
&vertex_shader_module,
|
||||||
&init_shader_module,
|
&init_shader_module,
|
||||||
|
context.texture_format(),
|
||||||
);
|
);
|
||||||
let step_pipeline = create_pipeline(
|
let step_pipeline = create_pipeline(
|
||||||
device,
|
device,
|
||||||
@@ -105,6 +106,7 @@ impl SdfPipeline {
|
|||||||
&pipeline_layout,
|
&pipeline_layout,
|
||||||
&vertex_shader_module,
|
&vertex_shader_module,
|
||||||
&step_shader_module,
|
&step_shader_module,
|
||||||
|
context.texture_format(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@@ -121,16 +123,42 @@ impl SdfPipeline {
|
|||||||
source_texture: &wgpu::Texture,
|
source_texture: &wgpu::Texture,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
|
) -> SignedDistanceFieldTextures {
|
||||||
|
let mut encoder =
|
||||||
|
context
|
||||||
|
.device()
|
||||||
|
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||||
|
label: Some("gpu-sdf-command-encoder"),
|
||||||
|
});
|
||||||
|
let textures = self.compute_signed_distance_field_with_encoder(
|
||||||
|
context,
|
||||||
|
&mut encoder,
|
||||||
|
source_texture,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
);
|
||||||
|
context.queue().submit([encoder.finish()]);
|
||||||
|
textures
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compute_signed_distance_field_with_encoder(
|
||||||
|
&self,
|
||||||
|
context: &GpuContext,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
source_texture: &wgpu::Texture,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
) -> SignedDistanceFieldTextures {
|
) -> SignedDistanceFieldTextures {
|
||||||
SignedDistanceFieldTextures {
|
SignedDistanceFieldTextures {
|
||||||
inside_texture: self.run_jfa(context, source_texture, width, height, false),
|
inside_texture: self.run_jfa(context, encoder, source_texture, width, height, false),
|
||||||
outside_texture: self.run_jfa(context, source_texture, width, height, true),
|
outside_texture: self.run_jfa(context, encoder, source_texture, width, height, true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_jfa(
|
fn run_jfa(
|
||||||
&self,
|
&self,
|
||||||
context: &GpuContext,
|
context: &GpuContext,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
source_texture: &wgpu::Texture,
|
source_texture: &wgpu::Texture,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
@@ -141,6 +169,7 @@ impl SdfPipeline {
|
|||||||
|
|
||||||
self.run_pass(
|
self.run_pass(
|
||||||
context,
|
context,
|
||||||
|
encoder,
|
||||||
source_texture,
|
source_texture,
|
||||||
&ping_texture,
|
&ping_texture,
|
||||||
&self.init_pipeline,
|
&self.init_pipeline,
|
||||||
@@ -167,6 +196,7 @@ impl SdfPipeline {
|
|||||||
};
|
};
|
||||||
self.run_pass(
|
self.run_pass(
|
||||||
context,
|
context,
|
||||||
|
encoder,
|
||||||
input_texture,
|
input_texture,
|
||||||
output_texture,
|
output_texture,
|
||||||
&self.step_pipeline,
|
&self.step_pipeline,
|
||||||
@@ -189,6 +219,7 @@ impl SdfPipeline {
|
|||||||
fn run_pass(
|
fn run_pass(
|
||||||
&self,
|
&self,
|
||||||
context: &GpuContext,
|
context: &GpuContext,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
input_texture: &wgpu::Texture,
|
input_texture: &wgpu::Texture,
|
||||||
output_texture: &wgpu::Texture,
|
output_texture: &wgpu::Texture,
|
||||||
pipeline: &wgpu::RenderPipeline,
|
pipeline: &wgpu::RenderPipeline,
|
||||||
@@ -230,12 +261,6 @@ impl SdfPipeline {
|
|||||||
resource: uniform_buffer.as_entire_binding(),
|
resource: uniform_buffer.as_entire_binding(),
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
let mut encoder =
|
|
||||||
context
|
|
||||||
.device()
|
|
||||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
|
||||||
label: Some("gpu-sdf-command-encoder"),
|
|
||||||
});
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
@@ -260,8 +285,6 @@ impl SdfPipeline {
|
|||||||
render_pass.set_bind_group(1, &uniform_bind_group, &[]);
|
render_pass.set_bind_group(1, &uniform_bind_group, &[]);
|
||||||
render_pass.draw(0..6, 0..1);
|
render_pass.draw(0..6, 0..1);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.queue().submit([encoder.finish()]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,6 +294,7 @@ fn create_pipeline(
|
|||||||
layout: &wgpu::PipelineLayout,
|
layout: &wgpu::PipelineLayout,
|
||||||
vertex_shader_module: &wgpu::ShaderModule,
|
vertex_shader_module: &wgpu::ShaderModule,
|
||||||
fragment_shader_module: &wgpu::ShaderModule,
|
fragment_shader_module: &wgpu::ShaderModule,
|
||||||
|
texture_format: wgpu::TextureFormat,
|
||||||
) -> wgpu::RenderPipeline {
|
) -> wgpu::RenderPipeline {
|
||||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
label: Some(label),
|
label: Some(label),
|
||||||
@@ -293,7 +317,7 @@ fn create_pipeline(
|
|||||||
module: fragment_shader_module,
|
module: fragment_shader_module,
|
||||||
entry_point: Some("fragment_main"),
|
entry_point: Some("fragment_main"),
|
||||||
targets: &[Some(wgpu::ColorTargetState {
|
targets: &[Some(wgpu::ColorTargetState {
|
||||||
format: GPU_TEXTURE_FORMAT,
|
format: texture_format,
|
||||||
blend: None,
|
blend: None,
|
||||||
write_mask: wgpu::ColorWrites::ALL,
|
write_mask: wgpu::ColorWrites::ALL,
|
||||||
})],
|
})],
|
||||||
|
|||||||
@@ -24,7 +24,10 @@ fn encode_seed(seed: vec2f) -> vec4f {
|
|||||||
@fragment
|
@fragment
|
||||||
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
||||||
let mask = textureSample(input_texture, input_sampler, input.tex_coord).r;
|
let mask = textureSample(input_texture, input_sampler, input.tex_coord).r;
|
||||||
let is_seed = select(mask > 0.5, mask < 0.5, uniforms.invert > 0.5);
|
let above = mask > 0.5;
|
||||||
|
let below = mask < 0.5;
|
||||||
|
let inverted = uniforms.invert > 0.5;
|
||||||
|
let is_seed = select(above, below, inverted);
|
||||||
|
|
||||||
if (is_seed) {
|
if (is_seed) {
|
||||||
let pixel_coord = floor(input.tex_coord * uniforms.resolution);
|
let pixel_coord = floor(input.tex_coord * uniforms.resolution);
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let encoded = textureSample(input_texture, input_sampler, sample_uv);
|
let encoded = textureSampleLevel(input_texture, input_sampler, sample_uv, 0.0);
|
||||||
if (is_no_seed(encoded)) {
|
if (is_no_seed(encoded)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user