fix(text): show selection highlight
Render tinted SDF glyphs in the final xray pass so base text and wipeouts cannot occlude them.\n\nRefs #479
This commit is contained in:
parent
cff4903c1e
commit
a4235a1243
2 changed files with 91 additions and 70 deletions
|
|
@ -102,6 +102,8 @@ pub struct Pipeline {
|
||||||
/// SDF text-quad pipeline (Phase 2b): draws per-glyph quads sampling the
|
/// SDF text-quad pipeline (Phase 2b): draws per-glyph quads sampling the
|
||||||
/// shared glyph atlas. Fed only when `OCS_TEXT_SDF` is set (else no verts).
|
/// shared glyph atlas. Fed only when `OCS_TEXT_SDF` is set (else no verts).
|
||||||
text_pipeline: wgpu::RenderPipeline,
|
text_pipeline: wgpu::RenderPipeline,
|
||||||
|
/// Depth-independent variant used by selection / rollover highlighting.
|
||||||
|
text_highlight_pipeline: wgpu::RenderPipeline,
|
||||||
mesh_pipeline: wgpu::RenderPipeline,
|
mesh_pipeline: wgpu::RenderPipeline,
|
||||||
/// Depth-write-disabled variant of `mesh_pipeline` for non-opaque solids.
|
/// Depth-write-disabled variant of `mesh_pipeline` for non-opaque solids.
|
||||||
mesh_transparent_pipeline: wgpu::RenderPipeline,
|
mesh_transparent_pipeline: wgpu::RenderPipeline,
|
||||||
|
|
@ -1602,8 +1604,8 @@ impl Pipeline {
|
||||||
|
|
||||||
// ── Text (SDF glyph quads) ─────────────────────────────────────────
|
// ── Text (SDF glyph quads) ─────────────────────────────────────────
|
||||||
let text_atlas_bgl = text_gpu::TextAtlasGpu::bind_group_layout(device);
|
let text_atlas_bgl = text_gpu::TextAtlasGpu::bind_group_layout(device);
|
||||||
let text_pipeline =
|
let (text_pipeline, text_highlight_pipeline) =
|
||||||
text_gpu::create_pipeline(device, &frame_bgl, &text_atlas_bgl, format, MSAA_SAMPLES);
|
text_gpu::create_pipelines(device, &frame_bgl, &text_atlas_bgl, format, MSAA_SAMPLES);
|
||||||
|
|
||||||
let viewcube = ViewCubePipeline::new(device, queue, format);
|
let viewcube = ViewCubePipeline::new(device, queue, format);
|
||||||
|
|
||||||
|
|
@ -1745,6 +1747,7 @@ impl Pipeline {
|
||||||
hatch_compat_pipeline,
|
hatch_compat_pipeline,
|
||||||
image_pipeline,
|
image_pipeline,
|
||||||
text_pipeline,
|
text_pipeline,
|
||||||
|
text_highlight_pipeline,
|
||||||
text_atlas_bgl,
|
text_atlas_bgl,
|
||||||
text_atlas_gpu: None,
|
text_atlas_gpu: None,
|
||||||
text_vbuf: None,
|
text_vbuf: None,
|
||||||
|
|
@ -3619,14 +3622,13 @@ impl Pipeline {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Pass 5c: SDF text quads (drawn over wires) ────────────────────
|
// ── Pass 5c: SDF text quads (drawn over wires) ────────────────────
|
||||||
// The highlight buffer (selected / hovered text, tinted) is drawn in
|
// Selection / rollover text is drawn later with the selected-wire xray
|
||||||
// the same pass right after the base text so it recolours those glyphs.
|
// overlay, after wipeouts, so normal text cannot hide its own tint.
|
||||||
if let Some(atlas) = &self.text_atlas_gpu {
|
if let Some(atlas) = &self.text_atlas_gpu {
|
||||||
let have_base = self.text_vbuf.is_some() && self.text_vcount > 0;
|
let have_base = self.text_vbuf.is_some() && self.text_vcount > 0;
|
||||||
let have_hl = self.text_highlight_vbuf.is_some() && self.text_highlight_vcount > 0;
|
|
||||||
let have_preview =
|
let have_preview =
|
||||||
self.text_preview_vbuf.is_some() && self.text_preview_vcount > 0;
|
self.text_preview_vbuf.is_some() && self.text_preview_vcount > 0;
|
||||||
if have_base || have_hl || have_preview {
|
if have_base || have_preview {
|
||||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
label: Some("text.render_pass"),
|
label: Some("text.render_pass"),
|
||||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
|
|
@ -3660,12 +3662,6 @@ impl Pipeline {
|
||||||
pass.draw(0..self.text_vcount, 0..1);
|
pass.draw(0..self.text_vcount, 0..1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(hlbuf) = &self.text_highlight_vbuf {
|
|
||||||
if self.text_highlight_vcount > 0 {
|
|
||||||
pass.set_vertex_buffer(0, hlbuf.slice(..));
|
|
||||||
pass.draw(0..self.text_highlight_vcount, 0..1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Grip-drag / command-preview glyphs, drawn over the base text.
|
// Grip-drag / command-preview glyphs, drawn over the base text.
|
||||||
if let Some(pbuf) = &self.text_preview_vbuf {
|
if let Some(pbuf) = &self.text_preview_vbuf {
|
||||||
if self.text_preview_vcount > 0 {
|
if self.text_preview_vcount > 0 {
|
||||||
|
|
@ -3714,12 +3710,14 @@ impl Pipeline {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Pass 7: selected wire overlay pass ───────────────────────────
|
// ── Pass 7: selection overlay pass ───────────────────────────────
|
||||||
// Redraws selected wires with depth_compare=Always so they appear on
|
// Redraws selected wires and text with depth_compare=Always so both
|
||||||
// top of all other geometry at full brightness.
|
// appear on top of all other geometry at full brightness.
|
||||||
if !self.gpu_selected_wires.is_empty() {
|
let have_text_highlight =
|
||||||
|
self.text_highlight_vbuf.is_some() && self.text_highlight_vcount > 0;
|
||||||
|
if !self.gpu_selected_wires.is_empty() || have_text_highlight {
|
||||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
label: Some("wire_xray.render_pass"),
|
label: Some("selection_xray.render_pass"),
|
||||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
view: msaa,
|
view: msaa,
|
||||||
depth_slice: None,
|
depth_slice: None,
|
||||||
|
|
@ -3741,19 +3739,31 @@ impl Pipeline {
|
||||||
occlusion_query_set: None,
|
occlusion_query_set: None,
|
||||||
});
|
});
|
||||||
pass.set_viewport(0.0, 0.0, vp.width as f32, vp.height as f32, 0.0, 1.0);
|
pass.set_viewport(0.0, 0.0, vp.width as f32, vp.height as f32, 0.0, 1.0);
|
||||||
pass.set_pipeline(&self.wire_xray_pipeline);
|
|
||||||
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
|
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
|
||||||
pass.set_stencil_reference(stencil_ref);
|
pass.set_stencil_reference(stencil_ref);
|
||||||
for wire in &self.gpu_selected_wires {
|
if !self.gpu_selected_wires.is_empty() {
|
||||||
if wire.instance_count > 0 {
|
pass.set_pipeline(&self.wire_xray_pipeline);
|
||||||
if let Some(bg) = &wire.const_bind_group {
|
for wire in &self.gpu_selected_wires {
|
||||||
pass.set_bind_group(1, bg.as_ref(), &[]);
|
if wire.instance_count > 0 {
|
||||||
|
if let Some(bg) = &wire.const_bind_group {
|
||||||
|
pass.set_bind_group(1, bg.as_ref(), &[]);
|
||||||
|
}
|
||||||
|
pass.set_vertex_buffer(0, wire.instance_buffer.slice(..));
|
||||||
|
pass.draw(
|
||||||
|
0..6,
|
||||||
|
wire.first_instance..wire.first_instance + wire.instance_count,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
pass.set_vertex_buffer(0, wire.instance_buffer.slice(..));
|
}
|
||||||
pass.draw(
|
}
|
||||||
0..6,
|
if let (Some(atlas), Some(hlbuf)) =
|
||||||
wire.first_instance..wire.first_instance + wire.instance_count,
|
(&self.text_atlas_gpu, &self.text_highlight_vbuf)
|
||||||
);
|
{
|
||||||
|
if self.text_highlight_vcount > 0 {
|
||||||
|
pass.set_pipeline(&self.text_highlight_pipeline);
|
||||||
|
pass.set_bind_group(1, &atlas.bind_group, &[]);
|
||||||
|
pass.set_vertex_buffer(0, hlbuf.slice(..));
|
||||||
|
pass.draw(0..self.text_highlight_vcount, 0..1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -250,16 +250,17 @@ impl TextAtlasGpu {
|
||||||
|
|
||||||
// ── Pipeline ─────────────────────────────────────────────────────────────────
|
// ── Pipeline ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// Build the text render pipeline. Mirrors the image pipeline: alpha blending,
|
/// Build the base and highlight text pipelines. The base mirrors the image
|
||||||
/// `LessEqual` depth with write, 4x MSAA. `frame_bgl` is group 0 (shared
|
/// pipeline (`LessEqual` depth with write); the highlight variant always draws
|
||||||
/// uniforms), `atlas_bgl` is group 1 (the atlas).
|
/// without changing depth, matching the selected-wire xray overlay.
|
||||||
pub fn create_pipeline(
|
/// `frame_bgl` is group 0 (shared uniforms), `atlas_bgl` is group 1 (the atlas).
|
||||||
|
pub fn create_pipelines(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
frame_bgl: &wgpu::BindGroupLayout,
|
frame_bgl: &wgpu::BindGroupLayout,
|
||||||
atlas_bgl: &wgpu::BindGroupLayout,
|
atlas_bgl: &wgpu::BindGroupLayout,
|
||||||
color_format: wgpu::TextureFormat,
|
color_format: wgpu::TextureFormat,
|
||||||
sample_count: u32,
|
sample_count: u32,
|
||||||
) -> wgpu::RenderPipeline {
|
) -> (wgpu::RenderPipeline, wgpu::RenderPipeline) {
|
||||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||||
label: Some("text.wgsl"),
|
label: Some("text.wgsl"),
|
||||||
source: wgpu::ShaderSource::Wgsl(include_str!("../../shaders/text.wgsl").into()),
|
source: wgpu::ShaderSource::Wgsl(include_str!("../../shaders/text.wgsl").into()),
|
||||||
|
|
@ -269,44 +270,54 @@ pub fn create_pipeline(
|
||||||
bind_group_layouts: &[frame_bgl, atlas_bgl],
|
bind_group_layouts: &[frame_bgl, atlas_bgl],
|
||||||
push_constant_ranges: &[],
|
push_constant_ranges: &[],
|
||||||
});
|
});
|
||||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
let create = |label, depth_write_enabled, depth_compare| {
|
||||||
label: Some("text.pipeline"),
|
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
layout: Some(&layout),
|
label: Some(label),
|
||||||
vertex: wgpu::VertexState {
|
layout: Some(&layout),
|
||||||
module: &shader,
|
vertex: wgpu::VertexState {
|
||||||
entry_point: Some("vs_main"),
|
module: &shader,
|
||||||
buffers: &[TextVertex::layout()],
|
entry_point: Some("vs_main"),
|
||||||
compilation_options: Default::default(),
|
buffers: &[TextVertex::layout()],
|
||||||
},
|
compilation_options: Default::default(),
|
||||||
fragment: Some(wgpu::FragmentState {
|
},
|
||||||
module: &shader,
|
fragment: Some(wgpu::FragmentState {
|
||||||
entry_point: Some("fs_main"),
|
module: &shader,
|
||||||
targets: &[Some(wgpu::ColorTargetState {
|
entry_point: Some("fs_main"),
|
||||||
format: color_format,
|
targets: &[Some(wgpu::ColorTargetState {
|
||||||
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
|
format: color_format,
|
||||||
write_mask: wgpu::ColorWrites::ALL,
|
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
|
||||||
})],
|
write_mask: wgpu::ColorWrites::ALL,
|
||||||
compilation_options: Default::default(),
|
})],
|
||||||
}),
|
compilation_options: Default::default(),
|
||||||
primitive: wgpu::PrimitiveState {
|
}),
|
||||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
primitive: wgpu::PrimitiveState {
|
||||||
..Default::default()
|
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||||
},
|
..Default::default()
|
||||||
depth_stencil: Some(wgpu::DepthStencilState {
|
},
|
||||||
format: wgpu::TextureFormat::Depth24PlusStencil8,
|
depth_stencil: Some(wgpu::DepthStencilState {
|
||||||
depth_write_enabled: true,
|
format: wgpu::TextureFormat::Depth24PlusStencil8,
|
||||||
depth_compare: wgpu::CompareFunction::LessEqual,
|
depth_write_enabled,
|
||||||
stencil: wgpu::StencilState::default(),
|
depth_compare,
|
||||||
bias: wgpu::DepthBiasState::default(),
|
stencil: wgpu::StencilState::default(),
|
||||||
}),
|
bias: wgpu::DepthBiasState::default(),
|
||||||
multisample: wgpu::MultisampleState {
|
}),
|
||||||
count: sample_count,
|
multisample: wgpu::MultisampleState {
|
||||||
mask: !0,
|
count: sample_count,
|
||||||
alpha_to_coverage_enabled: false,
|
mask: !0,
|
||||||
},
|
alpha_to_coverage_enabled: false,
|
||||||
multiview: None,
|
},
|
||||||
cache: None,
|
multiview: None,
|
||||||
})
|
cache: None,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
(
|
||||||
|
create("text.pipeline", true, wgpu::CompareFunction::LessEqual),
|
||||||
|
create(
|
||||||
|
"text.highlight.pipeline",
|
||||||
|
false,
|
||||||
|
wgpu::CompareFunction::Always,
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Upload a finished vertex list; `None` if empty.
|
/// Upload a finished vertex list; `None` if empty.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue