mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-19 06:39:13 +00:00
ifcviewer: move edge silhouette subsystem into ViewportCore (#84-s)
buildEdgePipeline, encodeEdgePass, releaseEdgeResources + the EDGE_WGSL shader source all move to ViewportCore. The edge_bind_group_ + the edges_enabled_ flag come along too (the latter aliased on VW so the edge-toggle keybind keeps compiling). The pass binds the now-core-side depth_view_ directly, so there's no remaining cross-side state dependency for edge rendering. render() still calls core_.encodeEdgePass(enc, surface_view) — once render() itself moves, the call collapses to a sibling method invocation.
This commit is contained in:
@@ -3447,3 +3447,179 @@ void ViewportCore::releaseMsaaColorTexture() {
|
|||||||
if (msaa_color_texture_) { wgpuTextureRelease(msaa_color_texture_); msaa_color_texture_ = nullptr; }
|
if (msaa_color_texture_) { wgpuTextureRelease(msaa_color_texture_); msaa_color_texture_ = nullptr; }
|
||||||
msaa_w_ = msaa_h_ = 0;
|
msaa_w_ = msaa_h_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Edge silhouette post-process (#84-s): buildEdgePipeline + encodeEdgePass +
|
||||||
|
// releaseEdgeResources
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
const char* EDGE_WGSL = R"(
|
||||||
|
@group(0) @binding(0) var src_depth: texture_depth_multisampled_2d;
|
||||||
|
|
||||||
|
const NEAR: f32 = 0.1;
|
||||||
|
const FAR: f32 = 10000.0;
|
||||||
|
const EDGE_SCALE: f32 = 6.0;
|
||||||
|
const EDGE_THRESHOLD: f32 = 0.004;
|
||||||
|
|
||||||
|
// Depth texture stores [0,1] z (we pre-multiply a z-remap onto Qt's GL-style
|
||||||
|
// projection in the main pipeline). Convert back to GL-NDC then reverse-
|
||||||
|
// project to view-space distance.
|
||||||
|
fn linearise(z: f32) -> f32 {
|
||||||
|
let ndc = z * 2.0 - 1.0;
|
||||||
|
return (2.0 * NEAR * FAR) / (FAR + NEAR - ndc * (FAR - NEAR));
|
||||||
|
}
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vs_main(@builtin(vertex_index) vid: u32) -> @builtin(position) vec4<f32> {
|
||||||
|
let x = f32((vid << 1u) & 2u) * 2.0 - 1.0;
|
||||||
|
let y = f32(vid & 2u) * 2.0 - 1.0;
|
||||||
|
return vec4<f32>(x, y, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main(@builtin(position) frag: vec4<f32>) -> @location(0) vec4<f32> {
|
||||||
|
let p = vec2<i32>(i32(frag.x), i32(frag.y));
|
||||||
|
let dim = vec2<i32>(textureDimensions(src_depth));
|
||||||
|
|
||||||
|
let dc_raw = textureLoad(src_depth, p, 0);
|
||||||
|
// Background pixels: nothing was drawn here. Skip so we don't draw
|
||||||
|
// edges on the void / sky.
|
||||||
|
if (dc_raw >= 0.99999) { discard; }
|
||||||
|
|
||||||
|
let c = linearise(dc_raw);
|
||||||
|
let n = linearise(textureLoad(src_depth, vec2<i32>(p.x, max(p.y - 1, 0)), 0));
|
||||||
|
let s = linearise(textureLoad(src_depth, vec2<i32>(p.x, min(p.y + 1, dim.y - 1)), 0));
|
||||||
|
let e = linearise(textureLoad(src_depth, vec2<i32>(min(p.x + 1, dim.x - 1), p.y), 0));
|
||||||
|
let w = linearise(textureLoad(src_depth, vec2<i32>(max(p.x - 1, 0), p.y), 0));
|
||||||
|
|
||||||
|
let lap = abs(4.0 * c - n - s - e - w);
|
||||||
|
let t = EDGE_THRESHOLD * c;
|
||||||
|
let edge = clamp((lap - t) * EDGE_SCALE, 0.0, 0.6);
|
||||||
|
|
||||||
|
// Multiplicative blend (Dst, Zero): output rgb = (1 - edge), so the
|
||||||
|
// existing surface colour is multiplied by (1 - edge) per channel.
|
||||||
|
return vec4<f32>(vec3<f32>(1.0 - edge), 1.0);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool ViewportCore::buildEdgePipeline() {
|
||||||
|
WGPUBindGroupLayoutEntry entries[1] = {};
|
||||||
|
entries[0].binding = 0;
|
||||||
|
entries[0].visibility = WGPUShaderStage_Fragment;
|
||||||
|
entries[0].texture.sampleType = WGPUTextureSampleType_Depth;
|
||||||
|
entries[0].texture.viewDimension = WGPUTextureViewDimension_2D;
|
||||||
|
entries[0].texture.multisampled = 1;
|
||||||
|
|
||||||
|
WGPUBindGroupLayoutDescriptor bgl_desc = {};
|
||||||
|
bgl_desc.entryCount = 1;
|
||||||
|
bgl_desc.entries = entries;
|
||||||
|
bgl_desc.label = svFromCStr("ifcviewer-wgpu.edge_bgl");
|
||||||
|
edge_bgl_ = wgpuDeviceCreateBindGroupLayout(device_, &bgl_desc);
|
||||||
|
|
||||||
|
WGPUPipelineLayoutDescriptor pl_desc = {};
|
||||||
|
pl_desc.bindGroupLayoutCount = 1;
|
||||||
|
pl_desc.bindGroupLayouts = &edge_bgl_;
|
||||||
|
pl_desc.label = svFromCStr("ifcviewer-wgpu.edge_pipeline_layout");
|
||||||
|
edge_pipeline_layout_ = wgpuDeviceCreatePipelineLayout(device_, &pl_desc);
|
||||||
|
|
||||||
|
WGPUShaderSourceWGSL wgsl_src = {};
|
||||||
|
wgsl_src.chain.sType = WGPUSType_ShaderSourceWGSL;
|
||||||
|
wgsl_src.code = svFromCStr(EDGE_WGSL);
|
||||||
|
WGPUShaderModuleDescriptor sm_desc = {};
|
||||||
|
sm_desc.nextInChain = &wgsl_src.chain;
|
||||||
|
sm_desc.label = svFromCStr("ifcviewer-wgpu.edge_wgsl");
|
||||||
|
edge_shader_module_ = wgpuDeviceCreateShaderModule(device_, &sm_desc);
|
||||||
|
|
||||||
|
// Multiplicative blend (Dst, Zero): out.rgb = src.rgb * dst.rgb.
|
||||||
|
// Fragment outputs (1 - edge, 1 - edge, 1 - edge) so the existing
|
||||||
|
// surface colour is scaled per-channel — strictly darkens, never
|
||||||
|
// brightens. Matches GL's renderEdgePass (GL_DST_COLOR, GL_ZERO).
|
||||||
|
WGPUBlendState blend = {};
|
||||||
|
blend.color.srcFactor = WGPUBlendFactor_Dst;
|
||||||
|
blend.color.dstFactor = WGPUBlendFactor_Zero;
|
||||||
|
blend.color.operation = WGPUBlendOperation_Add;
|
||||||
|
blend.alpha.srcFactor = WGPUBlendFactor_Zero;
|
||||||
|
blend.alpha.dstFactor = WGPUBlendFactor_One;
|
||||||
|
blend.alpha.operation = WGPUBlendOperation_Add;
|
||||||
|
|
||||||
|
WGPUColorTargetState target = {};
|
||||||
|
target.format = surface_format_;
|
||||||
|
target.blend = &blend;
|
||||||
|
target.writeMask = WGPUColorWriteMask_All;
|
||||||
|
|
||||||
|
WGPUFragmentState frag = {};
|
||||||
|
frag.module = edge_shader_module_;
|
||||||
|
frag.entryPoint = svFromCStr("fs_main");
|
||||||
|
frag.targetCount = 1;
|
||||||
|
frag.targets = ⌖
|
||||||
|
|
||||||
|
WGPURenderPipelineDescriptor rp_desc = {};
|
||||||
|
rp_desc.layout = edge_pipeline_layout_;
|
||||||
|
rp_desc.label = svFromCStr("ifcviewer-wgpu.edge_pipeline");
|
||||||
|
rp_desc.vertex.module = edge_shader_module_;
|
||||||
|
rp_desc.vertex.entryPoint = svFromCStr("vs_main");
|
||||||
|
rp_desc.vertex.bufferCount = 0;
|
||||||
|
rp_desc.fragment = &frag;
|
||||||
|
rp_desc.depthStencil = nullptr; // no depth attachment
|
||||||
|
rp_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
|
||||||
|
rp_desc.primitive.cullMode = WGPUCullMode_None;
|
||||||
|
rp_desc.multisample.count = 1;
|
||||||
|
rp_desc.multisample.mask = 0xFFFFFFFFu;
|
||||||
|
|
||||||
|
edge_pipeline_ = wgpuDeviceCreateRenderPipeline(device_, &rp_desc);
|
||||||
|
if (!edge_pipeline_) {
|
||||||
|
Log::warn() << "wgpu edge pipeline creation failed";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewportCore::encodeEdgePass(WGPUCommandEncoder enc,
|
||||||
|
WGPUTextureView surface_view) {
|
||||||
|
if (!edges_enabled_ || !edge_pipeline_ || !depth_view_ || !surface_view) return;
|
||||||
|
|
||||||
|
// Rebuild lazily when the underlying depth view was replaced (on
|
||||||
|
// resize we proactively null this alongside the HiZ bind group).
|
||||||
|
if (!edge_bind_group_) {
|
||||||
|
WGPUBindGroupEntry entry = {};
|
||||||
|
entry.binding = 0;
|
||||||
|
entry.textureView = depth_view_;
|
||||||
|
WGPUBindGroupDescriptor bg = {};
|
||||||
|
bg.layout = edge_bgl_;
|
||||||
|
bg.entryCount = 1;
|
||||||
|
bg.entries = &entry;
|
||||||
|
bg.label = svFromCStr("ifcviewer-wgpu.edge_bind_group");
|
||||||
|
edge_bind_group_ = wgpuDeviceCreateBindGroup(device_, &bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
WGPURenderPassColorAttachment color = {};
|
||||||
|
color.view = surface_view;
|
||||||
|
color.loadOp = WGPULoadOp_Load;
|
||||||
|
color.storeOp = WGPUStoreOp_Store;
|
||||||
|
color.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
|
||||||
|
|
||||||
|
WGPURenderPassDescriptor pass_desc = {};
|
||||||
|
pass_desc.colorAttachmentCount = 1;
|
||||||
|
pass_desc.colorAttachments = &color;
|
||||||
|
pass_desc.depthStencilAttachment = nullptr;
|
||||||
|
pass_desc.label = svFromCStr("ifcviewer-wgpu.edge_pass");
|
||||||
|
|
||||||
|
WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(enc, &pass_desc);
|
||||||
|
wgpuRenderPassEncoderSetPipeline(pass, edge_pipeline_);
|
||||||
|
wgpuRenderPassEncoderSetBindGroup(pass, 0, edge_bind_group_, 0, nullptr);
|
||||||
|
wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0);
|
||||||
|
wgpuRenderPassEncoderEnd(pass);
|
||||||
|
wgpuRenderPassEncoderRelease(pass);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewportCore::releaseEdgeResources() {
|
||||||
|
if (edge_bind_group_) { wgpuBindGroupRelease(edge_bind_group_); edge_bind_group_ = nullptr; }
|
||||||
|
if (edge_pipeline_) { wgpuRenderPipelineRelease(edge_pipeline_); edge_pipeline_ = nullptr; }
|
||||||
|
if (edge_shader_module_) { wgpuShaderModuleRelease(edge_shader_module_);edge_shader_module_ = nullptr; }
|
||||||
|
if (edge_pipeline_layout_) { wgpuPipelineLayoutRelease(edge_pipeline_layout_); edge_pipeline_layout_ = nullptr; }
|
||||||
|
if (edge_bgl_) { wgpuBindGroupLayoutRelease(edge_bgl_); edge_bgl_ = nullptr; }
|
||||||
|
}
|
||||||
|
|||||||
@@ -375,6 +375,22 @@ public:
|
|||||||
void ensureMsaaColorTexture(int w, int h);
|
void ensureMsaaColorTexture(int w, int h);
|
||||||
void releaseMsaaColorTexture();
|
void releaseMsaaColorTexture();
|
||||||
|
|
||||||
|
// ---- Edge silhouette post-process (#84-s) -----------------------------
|
||||||
|
//
|
||||||
|
// Build the edge pipeline + shader + BGL. Run after initWgpu's
|
||||||
|
// device is up. Returns false on pipeline creation failure.
|
||||||
|
bool buildEdgePipeline();
|
||||||
|
|
||||||
|
// Encode the edge silhouette fullscreen pass into the supplied
|
||||||
|
// command encoder. No-op when edges_enabled_ is false or the
|
||||||
|
// pipeline / depth view / surface view is null. Lazily builds the
|
||||||
|
// bind group on first call after a surface resize.
|
||||||
|
void encodeEdgePass(WGPUCommandEncoder enc, WGPUTextureView surface_view);
|
||||||
|
|
||||||
|
// Tear down the edge pipeline + bind group + supporting state.
|
||||||
|
// Called from shutdown() before the device dies.
|
||||||
|
void releaseEdgeResources();
|
||||||
|
|
||||||
// ---- Cull (#84-p) -----------------------------------------------------
|
// ---- Cull (#84-p) -----------------------------------------------------
|
||||||
//
|
//
|
||||||
// Per-instance occlusion test, supplied by the caller. Wired by
|
// Per-instance occlusion test, supplied by the caller. Wired by
|
||||||
@@ -535,6 +551,8 @@ private:
|
|||||||
WGPUBindGroupLayout edge_bgl_ = nullptr;
|
WGPUBindGroupLayout edge_bgl_ = nullptr;
|
||||||
WGPUPipelineLayout edge_pipeline_layout_ = nullptr;
|
WGPUPipelineLayout edge_pipeline_layout_ = nullptr;
|
||||||
WGPURenderPipeline edge_pipeline_ = nullptr;
|
WGPURenderPipeline edge_pipeline_ = nullptr;
|
||||||
|
WGPUBindGroup edge_bind_group_ = nullptr;
|
||||||
|
bool edges_enabled_ = true;
|
||||||
|
|
||||||
// Pick pass. Reuses pipeline_layout_ — same set of bindings as the
|
// Pick pass. Reuses pipeline_layout_ — same set of bindings as the
|
||||||
// main pass since the pick fragment also vertex-pulls instance data.
|
// main pass since the pick fragment also vertex-pulls instance data.
|
||||||
|
|||||||
@@ -241,6 +241,8 @@ ViewportWindow::ViewportWindow(QWindow* parent)
|
|||||||
edge_bgl_ (core_.edge_bgl_),
|
edge_bgl_ (core_.edge_bgl_),
|
||||||
edge_pipeline_layout_ (core_.edge_pipeline_layout_),
|
edge_pipeline_layout_ (core_.edge_pipeline_layout_),
|
||||||
edge_pipeline_ (core_.edge_pipeline_),
|
edge_pipeline_ (core_.edge_pipeline_),
|
||||||
|
edge_bind_group_ (core_.edge_bind_group_),
|
||||||
|
edges_enabled_ (core_.edges_enabled_),
|
||||||
pick_pipeline_(core_.pick_pipeline_),
|
pick_pipeline_(core_.pick_pipeline_),
|
||||||
pool_ (core_.pool_),
|
pool_ (core_.pool_),
|
||||||
streaming_thread_(core_.streaming_thread_),
|
streaming_thread_(core_.streaming_thread_),
|
||||||
@@ -742,7 +744,7 @@ bool ViewportWindow::initWgpu() {
|
|||||||
// init haven't migrated yet) -------------------------------------
|
// init haven't migrated yet) -------------------------------------
|
||||||
if (!buildPipelines()) return false;
|
if (!buildPipelines()) return false;
|
||||||
if (!core_.buildHizPipeline()) return false;
|
if (!core_.buildHizPipeline()) return false;
|
||||||
if (!buildEdgePipeline()) return false;
|
if (!core_.buildEdgePipeline()) return false;
|
||||||
if (!overlays_.init(instance_, device_, queue_, surface_format_, SAMPLE_COUNT)) {
|
if (!overlays_.init(instance_, device_, queue_, surface_format_, SAMPLE_COUNT)) {
|
||||||
Log::warn() << "OverlayRenderer init failed";
|
Log::warn() << "OverlayRenderer init failed";
|
||||||
return false;
|
return false;
|
||||||
@@ -999,164 +1001,11 @@ void ViewportWindow::configureSurface(int width_px, int height_px) {
|
|||||||
// camera near/far are hard-coded to the viewport defaults (0.1 / 10000).
|
// camera near/far are hard-coded to the viewport defaults (0.1 / 10000).
|
||||||
// They'll move to a small uniform when AppSettings ports over.
|
// They'll move to a small uniform when AppSettings ports over.
|
||||||
|
|
||||||
static const char* EDGE_WGSL = R"(
|
// EDGE_WGSL moved to ViewportCore.cpp anon namespace (#84-s).
|
||||||
@group(0) @binding(0) var src_depth: texture_depth_multisampled_2d;
|
|
||||||
|
|
||||||
const NEAR: f32 = 0.1;
|
// buildEdgePipeline moved to ViewportCore (#84-s).
|
||||||
const FAR: f32 = 10000.0;
|
|
||||||
const EDGE_SCALE: f32 = 6.0;
|
|
||||||
const EDGE_THRESHOLD: f32 = 0.004;
|
|
||||||
|
|
||||||
// Depth texture stores [0,1] z (we pre-multiply a z-remap onto Qt's GL-style
|
// encodeEdgePass moved to ViewportCore (#84-s).
|
||||||
// projection in the main pipeline). Convert back to GL-NDC then reverse-
|
|
||||||
// project to view-space distance.
|
|
||||||
fn linearise(z: f32) -> f32 {
|
|
||||||
let ndc = z * 2.0 - 1.0;
|
|
||||||
return (2.0 * NEAR * FAR) / (FAR + NEAR - ndc * (FAR - NEAR));
|
|
||||||
}
|
|
||||||
|
|
||||||
@vertex
|
|
||||||
fn vs_main(@builtin(vertex_index) vid: u32) -> @builtin(position) vec4<f32> {
|
|
||||||
let x = f32((vid << 1u) & 2u) * 2.0 - 1.0;
|
|
||||||
let y = f32(vid & 2u) * 2.0 - 1.0;
|
|
||||||
return vec4<f32>(x, y, 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@fragment
|
|
||||||
fn fs_main(@builtin(position) frag: vec4<f32>) -> @location(0) vec4<f32> {
|
|
||||||
let p = vec2<i32>(i32(frag.x), i32(frag.y));
|
|
||||||
let dim = vec2<i32>(textureDimensions(src_depth));
|
|
||||||
|
|
||||||
let dc_raw = textureLoad(src_depth, p, 0);
|
|
||||||
// Background pixels: nothing was drawn here. Skip so we don't draw
|
|
||||||
// edges on the void / sky.
|
|
||||||
if (dc_raw >= 0.99999) { discard; }
|
|
||||||
|
|
||||||
let c = linearise(dc_raw);
|
|
||||||
let n = linearise(textureLoad(src_depth, vec2<i32>(p.x, max(p.y - 1, 0)), 0));
|
|
||||||
let s = linearise(textureLoad(src_depth, vec2<i32>(p.x, min(p.y + 1, dim.y - 1)), 0));
|
|
||||||
let e = linearise(textureLoad(src_depth, vec2<i32>(min(p.x + 1, dim.x - 1), p.y), 0));
|
|
||||||
let w = linearise(textureLoad(src_depth, vec2<i32>(max(p.x - 1, 0), p.y), 0));
|
|
||||||
|
|
||||||
let lap = abs(4.0 * c - n - s - e - w);
|
|
||||||
let t = EDGE_THRESHOLD * c;
|
|
||||||
let edge = clamp((lap - t) * EDGE_SCALE, 0.0, 0.6);
|
|
||||||
|
|
||||||
// Multiplicative blend (Dst, Zero): output rgb = (1 - edge), so the
|
|
||||||
// existing surface colour is multiplied by (1 - edge) per channel.
|
|
||||||
return vec4<f32>(vec3<f32>(1.0 - edge), 1.0);
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
bool ViewportWindow::buildEdgePipeline() {
|
|
||||||
WGPUBindGroupLayoutEntry entries[1] = {};
|
|
||||||
entries[0].binding = 0;
|
|
||||||
entries[0].visibility = WGPUShaderStage_Fragment;
|
|
||||||
entries[0].texture.sampleType = WGPUTextureSampleType_Depth;
|
|
||||||
entries[0].texture.viewDimension = WGPUTextureViewDimension_2D;
|
|
||||||
entries[0].texture.multisampled = 1;
|
|
||||||
|
|
||||||
WGPUBindGroupLayoutDescriptor bgl_desc = {};
|
|
||||||
bgl_desc.entryCount = 1;
|
|
||||||
bgl_desc.entries = entries;
|
|
||||||
bgl_desc.label = svFromCStr("ifcviewer-wgpu.edge_bgl");
|
|
||||||
edge_bgl_ = wgpuDeviceCreateBindGroupLayout(device_, &bgl_desc);
|
|
||||||
|
|
||||||
WGPUPipelineLayoutDescriptor pl_desc = {};
|
|
||||||
pl_desc.bindGroupLayoutCount = 1;
|
|
||||||
pl_desc.bindGroupLayouts = &edge_bgl_;
|
|
||||||
pl_desc.label = svFromCStr("ifcviewer-wgpu.edge_pipeline_layout");
|
|
||||||
edge_pipeline_layout_ = wgpuDeviceCreatePipelineLayout(device_, &pl_desc);
|
|
||||||
|
|
||||||
WGPUShaderSourceWGSL wgsl_src = {};
|
|
||||||
wgsl_src.chain.sType = WGPUSType_ShaderSourceWGSL;
|
|
||||||
wgsl_src.code = svFromCStr(EDGE_WGSL);
|
|
||||||
WGPUShaderModuleDescriptor sm_desc = {};
|
|
||||||
sm_desc.nextInChain = &wgsl_src.chain;
|
|
||||||
sm_desc.label = svFromCStr("ifcviewer-wgpu.edge_wgsl");
|
|
||||||
edge_shader_module_ = wgpuDeviceCreateShaderModule(device_, &sm_desc);
|
|
||||||
|
|
||||||
// Multiplicative blend (Dst, Zero): out.rgb = src.rgb * dst.rgb.
|
|
||||||
// Fragment outputs (1 - edge, 1 - edge, 1 - edge) so the existing
|
|
||||||
// surface colour is scaled per-channel — strictly darkens, never
|
|
||||||
// brightens. Matches GL's renderEdgePass (GL_DST_COLOR, GL_ZERO).
|
|
||||||
WGPUBlendState blend = {};
|
|
||||||
blend.color.srcFactor = WGPUBlendFactor_Dst;
|
|
||||||
blend.color.dstFactor = WGPUBlendFactor_Zero;
|
|
||||||
blend.color.operation = WGPUBlendOperation_Add;
|
|
||||||
blend.alpha.srcFactor = WGPUBlendFactor_Zero;
|
|
||||||
blend.alpha.dstFactor = WGPUBlendFactor_One;
|
|
||||||
blend.alpha.operation = WGPUBlendOperation_Add;
|
|
||||||
|
|
||||||
WGPUColorTargetState target = {};
|
|
||||||
target.format = surface_format_;
|
|
||||||
target.blend = &blend;
|
|
||||||
target.writeMask = WGPUColorWriteMask_All;
|
|
||||||
|
|
||||||
WGPUFragmentState frag = {};
|
|
||||||
frag.module = edge_shader_module_;
|
|
||||||
frag.entryPoint = svFromCStr("fs_main");
|
|
||||||
frag.targetCount = 1;
|
|
||||||
frag.targets = ⌖
|
|
||||||
|
|
||||||
WGPURenderPipelineDescriptor rp_desc = {};
|
|
||||||
rp_desc.layout = edge_pipeline_layout_;
|
|
||||||
rp_desc.label = svFromCStr("ifcviewer-wgpu.edge_pipeline");
|
|
||||||
rp_desc.vertex.module = edge_shader_module_;
|
|
||||||
rp_desc.vertex.entryPoint = svFromCStr("vs_main");
|
|
||||||
rp_desc.vertex.bufferCount = 0;
|
|
||||||
rp_desc.fragment = &frag;
|
|
||||||
rp_desc.depthStencil = nullptr; // no depth attachment
|
|
||||||
rp_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
|
|
||||||
rp_desc.primitive.cullMode = WGPUCullMode_None;
|
|
||||||
rp_desc.multisample.count = 1;
|
|
||||||
rp_desc.multisample.mask = 0xFFFFFFFFu;
|
|
||||||
|
|
||||||
edge_pipeline_ = wgpuDeviceCreateRenderPipeline(device_, &rp_desc);
|
|
||||||
if (!edge_pipeline_) {
|
|
||||||
Log::warn() << "wgpu edge pipeline creation failed";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ViewportWindow::encodeEdgePass(WGPUCommandEncoder enc,
|
|
||||||
WGPUTextureView surface_view) {
|
|
||||||
if (!edges_enabled_ || !edge_pipeline_ || !depth_view_ || !surface_view) return;
|
|
||||||
|
|
||||||
// Rebuild lazily when the underlying depth view was replaced (on resize
|
|
||||||
// we proactively null this out alongside the HiZ bind group).
|
|
||||||
if (!edge_bind_group_) {
|
|
||||||
WGPUBindGroupEntry entry = {};
|
|
||||||
entry.binding = 0;
|
|
||||||
entry.textureView = depth_view_;
|
|
||||||
WGPUBindGroupDescriptor bg = {};
|
|
||||||
bg.layout = edge_bgl_;
|
|
||||||
bg.entryCount = 1;
|
|
||||||
bg.entries = &entry;
|
|
||||||
bg.label = svFromCStr("ifcviewer-wgpu.edge_bind_group");
|
|
||||||
edge_bind_group_ = wgpuDeviceCreateBindGroup(device_, &bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
WGPURenderPassColorAttachment color = {};
|
|
||||||
color.view = surface_view;
|
|
||||||
color.loadOp = WGPULoadOp_Load; // preserve resolved main-pass colour
|
|
||||||
color.storeOp = WGPUStoreOp_Store;
|
|
||||||
color.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
|
|
||||||
|
|
||||||
WGPURenderPassDescriptor pass_desc = {};
|
|
||||||
pass_desc.colorAttachmentCount = 1;
|
|
||||||
pass_desc.colorAttachments = &color;
|
|
||||||
pass_desc.depthStencilAttachment = nullptr;
|
|
||||||
pass_desc.label = svFromCStr("ifcviewer-wgpu.edge_pass");
|
|
||||||
|
|
||||||
WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(enc, &pass_desc);
|
|
||||||
wgpuRenderPassEncoderSetPipeline(pass, edge_pipeline_);
|
|
||||||
wgpuRenderPassEncoderSetBindGroup(pass, 0, edge_bind_group_, 0, nullptr);
|
|
||||||
wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0);
|
|
||||||
wgpuRenderPassEncoderEnd(pass);
|
|
||||||
wgpuRenderPassEncoderRelease(pass);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
void ViewportWindow::setPivotIndicatorVisible(bool visible, int hide_after_ms) {
|
void ViewportWindow::setPivotIndicatorVisible(bool visible, int hide_after_ms) {
|
||||||
@@ -1177,13 +1026,7 @@ void ViewportWindow::setPivotIndicatorVisible(bool visible, int hide_after_ms) {
|
|||||||
}
|
}
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
}
|
}
|
||||||
void ViewportWindow::releaseEdgeResources() {
|
// releaseEdgeResources moved to ViewportCore (#84-s).
|
||||||
if (edge_bind_group_) { wgpuBindGroupRelease(edge_bind_group_); edge_bind_group_ = nullptr; }
|
|
||||||
if (edge_pipeline_) { wgpuRenderPipelineRelease(edge_pipeline_); edge_pipeline_ = nullptr; }
|
|
||||||
if (edge_shader_module_) { wgpuShaderModuleRelease(edge_shader_module_);edge_shader_module_ = nullptr; }
|
|
||||||
if (edge_pipeline_layout_) { wgpuPipelineLayoutRelease(edge_pipeline_layout_); edge_pipeline_layout_ = nullptr; }
|
|
||||||
if (edge_bgl_) { wgpuBindGroupLayoutRelease(edge_bgl_); edge_bgl_ = nullptr; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Pick pipeline (stage 4)
|
// Pick pipeline (stage 4)
|
||||||
@@ -2896,7 +2739,7 @@ void ViewportWindow::render() {
|
|||||||
// lines onto the resolved surface colour. Encoded before HiZ resolve
|
// lines onto the resolved surface colour. Encoded before HiZ resolve
|
||||||
// so HiZ uses the same MSAA depth that produced the edges.
|
// so HiZ uses the same MSAA depth that produced the edges.
|
||||||
if (edges_enabled_) {
|
if (edges_enabled_) {
|
||||||
encodeEdgePass(enc, view);
|
core_.encodeEdgePass(enc, view);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Corner axis gizmo. Encoded after the edge pass on the resolved
|
// Corner axis gizmo. Encoded after the edge pass on the resolved
|
||||||
@@ -4453,7 +4296,7 @@ void ViewportWindow::shutdown() {
|
|||||||
core_.releaseDepthTexture();
|
core_.releaseDepthTexture();
|
||||||
core_.releaseMsaaColorTexture();
|
core_.releaseMsaaColorTexture();
|
||||||
core_.releaseHizResources();
|
core_.releaseHizResources();
|
||||||
releaseEdgeResources();
|
core_.releaseEdgeResources();
|
||||||
overlays_.destroy();
|
overlays_.destroy();
|
||||||
releasePickResources();
|
releasePickResources();
|
||||||
|
|
||||||
|
|||||||
@@ -670,8 +670,8 @@ private:
|
|||||||
WGPUBindGroupLayout& edge_bgl_;
|
WGPUBindGroupLayout& edge_bgl_;
|
||||||
WGPUPipelineLayout& edge_pipeline_layout_;
|
WGPUPipelineLayout& edge_pipeline_layout_;
|
||||||
WGPURenderPipeline& edge_pipeline_;
|
WGPURenderPipeline& edge_pipeline_;
|
||||||
WGPUBindGroup edge_bind_group_ = nullptr;
|
WGPUBindGroup& edge_bind_group_;
|
||||||
bool edges_enabled_ = true;
|
bool& edges_enabled_;
|
||||||
|
|
||||||
// Pivot visibility state — the gizmo itself lives in overlays_.
|
// Pivot visibility state — the gizmo itself lives in overlays_.
|
||||||
// The timer auto-hides the pivot after a wheel-zoom afterglow.
|
// The timer auto-hides the pivot after a wheel-zoom afterglow.
|
||||||
|
|||||||
Reference in New Issue
Block a user