1use super::*;
2
3impl egui_wgpu::CallbackTrait for ViewportCallback {
4 fn paint(
5 &self,
6 _info: egui::PaintCallbackInfo,
7 render_pass: &mut wgpu::RenderPass<'static>,
8 resources: &egui_wgpu::CallbackResources,
9 ) {
10 let Some(vp) = resources.get::<ViewportPaint>() else {
11 return;
12 };
13 render_pass.set_pipeline(&vp.pipeline);
16 render_pass.set_bind_group(0, &vp.bind_group, &[]);
17 render_pass.draw(0..3, 0..1);
18 }
19}
20
21impl Viewport {
22 pub fn new(render_state: &egui_wgpu::RenderState) -> Self {
27 let device = render_state.device.clone();
28 let queue = render_state.queue.clone();
29 let core = RenderCore::new(device.clone(), queue, COLOR_FORMAT);
30 let gpu_scene = GpuScene::default();
31
32 let target_format = render_state.target_format;
34 let blit_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
35 label: Some("brep-app viewport blit"),
36 entries: &[
37 wgpu::BindGroupLayoutEntry {
38 binding: 0,
39 visibility: wgpu::ShaderStages::FRAGMENT,
40 ty: wgpu::BindingType::Texture {
41 sample_type: wgpu::TextureSampleType::Float { filterable: true },
42 view_dimension: wgpu::TextureViewDimension::D2,
43 multisampled: false,
44 },
45 count: None,
46 },
47 wgpu::BindGroupLayoutEntry {
48 binding: 1,
49 visibility: wgpu::ShaderStages::FRAGMENT,
50 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
51 count: None,
52 },
53 ],
54 });
55 let blit_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
56 label: Some("brep-app viewport sampler"),
57 mag_filter: wgpu::FilterMode::Nearest,
58 min_filter: wgpu::FilterMode::Nearest,
59 ..Default::default()
60 });
61 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
62 label: Some("brep-app blit"),
63 source: wgpu::ShaderSource::Wgsl(blit_wgsl(target_format.is_srgb()).into()),
64 });
65 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
66 label: Some("brep-app blit layout"),
67 bind_group_layouts: &[Some(&blit_layout)],
68 immediate_size: 0,
69 });
70 let blit_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
71 label: Some("brep-app blit pipeline"),
72 layout: Some(&pipeline_layout),
73 vertex: wgpu::VertexState {
74 module: &shader,
75 entry_point: Some("vs"),
76 compilation_options: Default::default(),
77 buffers: &[],
78 },
79 fragment: Some(wgpu::FragmentState {
80 module: &shader,
81 entry_point: Some("fs"),
82 compilation_options: Default::default(),
83 targets: &[Some(wgpu::ColorTargetState {
84 format: target_format,
85 blend: None,
86 write_mask: wgpu::ColorWrites::ALL,
87 })],
88 }),
89 primitive: wgpu::PrimitiveState {
90 topology: wgpu::PrimitiveTopology::TriangleList,
91 ..Default::default()
92 },
93 depth_stencil: None,
94 multisample: wgpu::MultisampleState::default(),
97 multiview_mask: None,
98 cache: None,
99 });
100
101 Self {
102 core,
103 gpu_scene,
104 egui_renderer: render_state.renderer.clone(),
105 blit_layout,
106 blit_sampler,
107 blit_pipeline,
108 offscreen: None,
109 dragging: false,
110 gizmo_dragging: false,
111 component_gizmo_dragging: false,
112 dim_dragging: None,
113 sketch_dragging: false,
114 sketch_handdrawing: false,
115 last_rect: None,
116 candidate_popup: None,
117 candidate_popup_fresh: false,
118 candidate_popup_rect: None,
119 candidate_hits: Vec::new(),
120 editing_dim: None,
121 dim_edit_fresh: false,
122 editing_feature_dim: None,
123 feature_dim_edit_fresh: false,
124 constraint_dragging: false,
125 constraint_label_hovered: None,
126 pmi_label_hovered: None,
127 pmi_label_dragging: None,
128 pmi_label_hits: Vec::new(),
129 }
130 }
131
132 pub(super) fn ensure_offscreen(&mut self, w: u32, h: u32) {
135 if let Some(off) = &self.offscreen {
136 if off.w == w && off.h == h {
137 return;
138 }
139 }
140 let texture = self.core.device.create_texture(&wgpu::TextureDescriptor {
141 label: Some("brep-app viewport offscreen"),
142 size: wgpu::Extent3d {
143 width: w,
144 height: h,
145 depth_or_array_layers: 1,
146 },
147 mip_level_count: 1,
148 sample_count: 1,
149 dimension: wgpu::TextureDimension::D2,
150 format: COLOR_FORMAT,
151 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
152 view_formats: &[],
153 });
154 let view = texture.create_view(&Default::default());
155 let bind_group = self.core.device.create_bind_group(&wgpu::BindGroupDescriptor {
156 label: Some("brep-app viewport blit bind group"),
157 layout: &self.blit_layout,
158 entries: &[
159 wgpu::BindGroupEntry {
160 binding: 0,
161 resource: wgpu::BindingResource::TextureView(&view),
162 },
163 wgpu::BindGroupEntry {
164 binding: 1,
165 resource: wgpu::BindingResource::Sampler(&self.blit_sampler),
166 },
167 ],
168 });
169 self.egui_renderer
171 .write()
172 .callback_resources
173 .insert(ViewportPaint {
174 pipeline: self.blit_pipeline.clone(),
175 bind_group,
176 });
177 self.offscreen = Some(Offscreen { view, w, h });
178 }
179
180 pub(super) fn render_viewport(&mut self, phys_w: u32, phys_h: u32, ppp: f32, state: &mut EngineState) {
184 let Some(off) = &self.offscreen else { return };
185 self.core.sync_scene(
186 &mut self.gpu_scene,
187 &state.scene,
188 &state.settings,
189 state.settings_generation,
190 );
191 let (camera, overlay) = state.fit_camera_and_overlay();
197 let params = FrameParams {
198 camera: &camera,
199 width: phys_w,
200 height: phys_h,
201 dpr: ppp,
202 settings: &state.settings,
203 emphasis: &state.emphasis,
204 world_per_pixel: state.camera.world_per_pixel(),
205 overlay: overlay.as_ref(),
206 };
207 self.core
208 .render_to_view(&mut self.gpu_scene, &state.scene, ¶ms, &off.view);
209 state.dirty = false;
210 }
211}
212
213fn blit_wgsl(target_is_srgb: bool) -> String {
217 let frag_body = if target_is_srgb {
218 r#"
221 let c = textureSample(tex, samp, in.uv);
222 let rgb = srgb_to_linear(c.rgb);
223 return vec4<f32>(rgb, c.a);
224"#
225 } else {
226 r#"
227 return textureSample(tex, samp, in.uv);
228"#
229 };
230 format!(
231 r#"
232@group(0) @binding(0) var tex: texture_2d<f32>;
233@group(0) @binding(1) var samp: sampler;
234
235struct VsOut {{
236 @builtin(position) pos: vec4<f32>,
237 @location(0) uv: vec2<f32>,
238}};
239
240@vertex
241fn vs(@builtin(vertex_index) vi: u32) -> VsOut {{
242 var p = array<vec2<f32>, 3>(
243 vec2<f32>(-1.0, -1.0),
244 vec2<f32>( 3.0, -1.0),
245 vec2<f32>(-1.0, 3.0),
246 );
247 var out: VsOut;
248 let xy = p[vi];
249 out.pos = vec4<f32>(xy, 0.0, 1.0);
250 // Framebuffer origin is top-left: map clip +y (top) -> uv.y 0.
251 out.uv = vec2<f32>((xy.x + 1.0) * 0.5, (1.0 - xy.y) * 0.5);
252 return out;
253}}
254
255fn srgb_to_linear(c: vec3<f32>) -> vec3<f32> {{
256 let lo = c / 12.92;
257 let hi = pow((c + 0.055) / 1.055, vec3<f32>(2.4));
258 return select(hi, lo, c <= vec3<f32>(0.04045));
259}}
260
261@fragment
262fn fs(in: VsOut) -> @location(0) vec4<f32> {{{frag_body}}}
263"#
264 )
265}