diff --git a/src/ifcviewer-wgpu/WgpuOverlayRenderer.cpp b/src/ifcviewer-wgpu/WgpuOverlayRenderer.cpp index c9ad7e2e00..8db8557cca 100644 --- a/src/ifcviewer-wgpu/WgpuOverlayRenderer.cpp +++ b/src/ifcviewer-wgpu/WgpuOverlayRenderer.cpp @@ -19,6 +19,11 @@ #include "WgpuOverlayRenderer.h" +#include +#include +#include +#include +#include #include #include @@ -375,6 +380,34 @@ fn fs_main(in: VsOut) -> @location(0) vec4 { } )WGSL"; +// Label shader: textured quads in screen space. Each visible label/HUD +// item contributes 6 vertices (NDC position + uv); a pre-rasterised +// QImage carrying both the dark-grey background fill and the white +// text occupies the bound texture. Standard alpha blend. +static const char* LABELS_WGSL = R"WGSL( +@group(0) @binding(0) var samp: sampler; +@group(0) @binding(1) var tex: texture_2d; + +struct VsOut { + @builtin(position) clip_pos: vec4, + @location(0) uv: vec2, +}; + +@vertex +fn vs_main(@location(0) ndc: vec2, + @location(1) uv: vec2) -> VsOut { + var out: VsOut; + out.clip_pos = vec4(ndc, 0.0, 1.0); + out.uv = uv; + return out; +} + +@fragment +fn fs_main(in: VsOut) -> @location(0) vec4 { + return textureSample(tex, samp, in.uv); +} +)WGSL"; + // ----------------------------------------------------------------------------- // Construction / destruction // ----------------------------------------------------------------------------- @@ -396,6 +429,7 @@ bool WgpuOverlayRenderer::init(WGPUInstance instance, WGPUDevice device, if (!buildMarquee()) return false; if (!buildOverlayLines()) return false; if (!buildOverlayPoints()) return false; + if (!buildLabels()) return false; return true; } @@ -453,6 +487,18 @@ void WgpuOverlayRenderer::destroy() { if (overlay_point_vertex_buffer_) { wgpuBufferRelease(overlay_point_vertex_buffer_); overlay_point_vertex_buffer_ = nullptr; } overlay_point_vertex_capacity_ = 0; overlay_point_vertex_count_ = 0; + + // Labels + HUD + releaseLabelTextures(); + if (label_sampler_) { wgpuSamplerRelease(label_sampler_); label_sampler_ = nullptr; } + if (label_pipeline_) { wgpuRenderPipelineRelease(label_pipeline_); label_pipeline_ = nullptr; } + if (label_shader_module_) { wgpuShaderModuleRelease(label_shader_module_); label_shader_module_ = nullptr; } + if (label_pipeline_layout_) { wgpuPipelineLayoutRelease(label_pipeline_layout_); label_pipeline_layout_ = nullptr; } + if (label_bgl_) { wgpuBindGroupLayoutRelease(label_bgl_); label_bgl_ = nullptr; } + if (label_vertex_buffer_) { wgpuBufferRelease(label_vertex_buffer_); label_vertex_buffer_ = nullptr; } + label_vertex_capacity_ = 0; + labels_.clear(); + hud_text_.clear(); } // ----------------------------------------------------------------------------- @@ -1647,3 +1693,356 @@ void WgpuOverlayRenderer::encodeOverlayPoints(WGPURenderPassEncoder pass, 0, WGPU_WHOLE_SIZE); wgpuRenderPassEncoderDraw(pass, overlay_point_vertex_count_, 1, 0, 0); } + +// ----------------------------------------------------------------------------- +// Labels + HUD text (textured quads, content-cached) +// ----------------------------------------------------------------------------- + +bool WgpuOverlayRenderer::buildLabels() { + { + WGPUSamplerDescriptor sd = {}; + sd.minFilter = WGPUFilterMode_Linear; + sd.magFilter = WGPUFilterMode_Linear; + sd.mipmapFilter = WGPUMipmapFilterMode_Nearest; + sd.addressModeU = WGPUAddressMode_ClampToEdge; + sd.addressModeV = WGPUAddressMode_ClampToEdge; + sd.addressModeW = WGPUAddressMode_ClampToEdge; + sd.lodMinClamp = 0.0f; + sd.lodMaxClamp = 0.0f; + sd.maxAnisotropy = 1; + sd.label = svFromCStr("ifcviewer-wgpu.label_sampler"); + label_sampler_ = wgpuDeviceCreateSampler(device_, &sd); + } + { + WGPUBindGroupLayoutEntry entries[2] = {}; + entries[0].binding = 0; + entries[0].visibility = WGPUShaderStage_Fragment; + entries[0].sampler.type = WGPUSamplerBindingType_Filtering; + entries[1].binding = 1; + entries[1].visibility = WGPUShaderStage_Fragment; + entries[1].texture.sampleType = WGPUTextureSampleType_Float; + entries[1].texture.viewDimension = WGPUTextureViewDimension_2D; + WGPUBindGroupLayoutDescriptor bgl_desc = {}; + bgl_desc.entryCount = 2; + bgl_desc.entries = entries; + bgl_desc.label = svFromCStr("ifcviewer-wgpu.label_bgl"); + label_bgl_ = wgpuDeviceCreateBindGroupLayout(device_, &bgl_desc); + } + { + WGPUPipelineLayoutDescriptor pl_desc = {}; + pl_desc.bindGroupLayoutCount = 1; + pl_desc.bindGroupLayouts = &label_bgl_; + pl_desc.label = svFromCStr("ifcviewer-wgpu.label_pipeline_layout"); + label_pipeline_layout_ = wgpuDeviceCreatePipelineLayout(device_, &pl_desc); + } + { + WGPUShaderSourceWGSL wgsl_src = {}; + wgsl_src.chain.sType = WGPUSType_ShaderSourceWGSL; + wgsl_src.code = svFromCStr(LABELS_WGSL); + WGPUShaderModuleDescriptor sm_desc = {}; + sm_desc.nextInChain = &wgsl_src.chain; + sm_desc.label = svFromCStr("ifcviewer-wgpu.label_wgsl"); + label_shader_module_ = wgpuDeviceCreateShaderModule(device_, &sm_desc); + } + { + WGPUBufferDescriptor bdesc = {}; + bdesc.usage = WGPUBufferUsage_Vertex | WGPUBufferUsage_CopyDst; + bdesc.size = 256; + bdesc.label = svFromCStr("ifcviewer-wgpu.label_vbo"); + label_vertex_buffer_ = wgpuDeviceCreateBuffer(device_, &bdesc); + label_vertex_capacity_ = 256; + } + + // Per-vertex: vec2 NDC + vec2 uv = 16 B stride. + WGPUVertexAttribute attribs[2] = {}; + attribs[0].format = WGPUVertexFormat_Float32x2; attribs[0].offset = 0; attribs[0].shaderLocation = 0; + attribs[1].format = WGPUVertexFormat_Float32x2; attribs[1].offset = 8; attribs[1].shaderLocation = 1; + WGPUVertexBufferLayout vbl = {}; + vbl.arrayStride = 16; + vbl.stepMode = WGPUVertexStepMode_Vertex; + vbl.attributeCount = 2; + vbl.attributes = attribs; + + WGPUBlendState blend = {}; + blend.color.srcFactor = WGPUBlendFactor_SrcAlpha; + blend.color.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha; + blend.color.operation = WGPUBlendOperation_Add; + blend.alpha.srcFactor = WGPUBlendFactor_One; + blend.alpha.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha; + blend.alpha.operation = WGPUBlendOperation_Add; + + WGPUColorTargetState ct = {}; + ct.format = surface_format_; + ct.blend = &blend; + ct.writeMask = WGPUColorWriteMask_All; + + WGPUFragmentState frag = {}; + frag.module = label_shader_module_; + frag.entryPoint = svFromCStr("fs_main"); + frag.targetCount = 1; + frag.targets = &ct; + + WGPURenderPipelineDescriptor rp_desc = {}; + rp_desc.layout = label_pipeline_layout_; + rp_desc.label = svFromCStr("ifcviewer-wgpu.label_pipeline"); + rp_desc.vertex.module = label_shader_module_; + rp_desc.vertex.entryPoint = svFromCStr("vs_main"); + rp_desc.vertex.bufferCount = 1; + rp_desc.vertex.buffers = &vbl; + rp_desc.fragment = &frag; + rp_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList; + rp_desc.primitive.cullMode = WGPUCullMode_None; + rp_desc.multisample.count = 1; + rp_desc.multisample.mask = 0xFFFFFFFFu; + label_pipeline_ = wgpuDeviceCreateRenderPipeline(device_, &rp_desc); + return label_pipeline_ != nullptr; +} + +void WgpuOverlayRenderer::releaseLabelTextures() { + for (auto it = label_tex_cache_.begin(); it != label_tex_cache_.end(); ++it) { + if (it.value().bind_group) wgpuBindGroupRelease(it.value().bind_group); + if (it.value().view) wgpuTextureViewRelease(it.value().view); + if (it.value().texture) wgpuTextureRelease(it.value().texture); + } + label_tex_cache_.clear(); +} + +void WgpuOverlayRenderer::setOverlayLabels(const std::vector