mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-26 18:21:59 +00:00
ifcviewer: move configureSurface into ViewportCore (#84-u)
The swapchain configuration path — WGPU_PRESENT_MODE handling, mode-preference order (Mailbox / Immediate / FifoRelaxed / Fifo), caps probe, cfg.alphaMode wiring, and the on-resize depth + MSAA + HiZ texture reallocation + bind-group invalidation — all move to ViewportCore. The only VW-side concern was a QString::fromLatin1 in the advertised-modes log, which becomes a std::string concat with the same output. ViewportWindow's render() / resizeEvent / surface-outdated retry paths call core_.configureSurface() now. createSurface() (which returns the platform-specific WGPUSurface from the host) stays VW because Qt platform discovery has to happen on the Qt side.
This commit is contained in:
@@ -4450,3 +4450,135 @@ bool ViewportCore::raycast(const float origin[3], const float dir[3],
|
|||||||
out.world_normal[2] = best_normal[2];
|
out.world_normal[2] = best_normal[2];
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Surface configuration (#84-u): configureSurface
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
void ViewportCore::configureSurface(int width_px, int height_px) {
|
||||||
|
WGPUSurfaceConfiguration cfg = {};
|
||||||
|
cfg.device = device_;
|
||||||
|
cfg.format = surface_format_;
|
||||||
|
// CopySrc lets the screenshot path copy the surface texture back to
|
||||||
|
// host memory. Trivial cost on all known backends.
|
||||||
|
cfg.usage = WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_CopySrc;
|
||||||
|
cfg.width = std::uint32_t(width_px);
|
||||||
|
cfg.height = std::uint32_t(height_px);
|
||||||
|
|
||||||
|
// Present mode preference order: Mailbox → Immediate → FifoRelaxed
|
||||||
|
// → Fifo. Override with WGPU_PRESENT_MODE=...; otherwise pick the
|
||||||
|
// first mode the surface actually advertises (asking for one that
|
||||||
|
// isn't listed panics wgpu-native from Rust).
|
||||||
|
//
|
||||||
|
// Why Immediate sits above FifoRelaxed: Mailbox is the right answer
|
||||||
|
// for an interactive viewer (vsync-aligned, no tearing, 1-frame
|
||||||
|
// queue) but a meaningful subset of Linux Vulkan stacks (some
|
||||||
|
// compositors, some driver/WSI combinations) silently don't expose
|
||||||
|
// it. On those stacks Fifo's 2-3 frame queue doubles input-to-
|
||||||
|
// photon latency; Immediate can tear but keeps latency at one
|
||||||
|
// render-body. FifoRelaxed is the middle option.
|
||||||
|
WGPUPresentMode preferred[4] = {
|
||||||
|
WGPUPresentMode_Mailbox,
|
||||||
|
WGPUPresentMode_Immediate,
|
||||||
|
WGPUPresentMode_FifoRelaxed,
|
||||||
|
WGPUPresentMode_Fifo,
|
||||||
|
};
|
||||||
|
const char* pm_name = "mailbox";
|
||||||
|
if (const char* s = std::getenv("WGPU_PRESENT_MODE")) {
|
||||||
|
WGPUPresentMode override_pm = WGPUPresentMode_Fifo;
|
||||||
|
bool known = true;
|
||||||
|
if (std::strcmp(s, "fifo") == 0) { override_pm = WGPUPresentMode_Fifo; pm_name = "fifo"; }
|
||||||
|
else if (std::strcmp(s, "fifo_relaxed") == 0) { override_pm = WGPUPresentMode_FifoRelaxed; pm_name = "fifo_relaxed"; }
|
||||||
|
else if (std::strcmp(s, "mailbox") == 0) { override_pm = WGPUPresentMode_Mailbox; pm_name = "mailbox"; }
|
||||||
|
else if (std::strcmp(s, "immediate") == 0) { override_pm = WGPUPresentMode_Immediate; pm_name = "immediate"; }
|
||||||
|
else {
|
||||||
|
known = false;
|
||||||
|
Log::warn()
|
||||||
|
<< "[wgpu] unknown WGPU_PRESENT_MODE=" << s
|
||||||
|
<< " (expected fifo|fifo_relaxed|mailbox|immediate);"
|
||||||
|
" falling back to preference order";
|
||||||
|
}
|
||||||
|
if (known) {
|
||||||
|
preferred[0] = override_pm;
|
||||||
|
preferred[1] = WGPUPresentMode_Fifo; // Fifo is the only guaranteed-supported mode
|
||||||
|
preferred[2] = preferred[3] = WGPUPresentMode_Fifo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WGPUSurfaceCapabilities caps = {};
|
||||||
|
wgpuSurfaceGetCapabilities(surface_, adapter_, &caps);
|
||||||
|
auto supports = [&](WGPUPresentMode mode) {
|
||||||
|
for (std::size_t i = 0; i < caps.presentModeCount; ++i) {
|
||||||
|
if (caps.presentModes[i] == mode) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Diagnostic: dump the full advertised set on first configure. If
|
||||||
|
// Mailbox is missing here the driver doesn't expose it (drives the
|
||||||
|
// input-latency story); if Mailbox is listed but we still pick
|
||||||
|
// Fifo, the preference order has a bug.
|
||||||
|
if (!surface_configured_) {
|
||||||
|
std::string advertised;
|
||||||
|
for (std::size_t i = 0; i < caps.presentModeCount; ++i) {
|
||||||
|
const char* name = "?";
|
||||||
|
switch (caps.presentModes[i]) {
|
||||||
|
case WGPUPresentMode_Fifo: name = "fifo"; break;
|
||||||
|
case WGPUPresentMode_FifoRelaxed: name = "fifo_relaxed"; break;
|
||||||
|
case WGPUPresentMode_Mailbox: name = "mailbox"; break;
|
||||||
|
case WGPUPresentMode_Immediate: name = "immediate"; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
if (i > 0) advertised += ", ";
|
||||||
|
advertised += name;
|
||||||
|
}
|
||||||
|
Log::info() << "[wgpu] surface advertises present modes: " << advertised;
|
||||||
|
}
|
||||||
|
WGPUPresentMode pm = WGPUPresentMode_Fifo; // spec-guaranteed fallback
|
||||||
|
for (WGPUPresentMode candidate : preferred) {
|
||||||
|
if (supports(candidate)) { pm = candidate; break; }
|
||||||
|
}
|
||||||
|
switch (pm) {
|
||||||
|
case WGPUPresentMode_Mailbox: pm_name = "mailbox"; break;
|
||||||
|
case WGPUPresentMode_FifoRelaxed: pm_name = "fifo_relaxed"; break;
|
||||||
|
case WGPUPresentMode_Immediate: pm_name = "immediate"; break;
|
||||||
|
case WGPUPresentMode_Fifo: pm_name = "fifo"; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
wgpuSurfaceCapabilitiesFreeMembers(caps);
|
||||||
|
cfg.presentMode = pm;
|
||||||
|
if (!surface_configured_) {
|
||||||
|
const char* note = "";
|
||||||
|
switch (pm) {
|
||||||
|
case WGPUPresentMode_Mailbox:
|
||||||
|
note = " (vsync-aligned, no queue lag -- default)"; break;
|
||||||
|
case WGPUPresentMode_Fifo:
|
||||||
|
note = " (strict vsync, may queue 2-3 frames)"; break;
|
||||||
|
case WGPUPresentMode_FifoRelaxed:
|
||||||
|
note = " (adaptive vsync -- sync if in budget, tear if not)"; break;
|
||||||
|
case WGPUPresentMode_Immediate:
|
||||||
|
note = " (vsync OFF -- framerate uncapped, may tear)"; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
Log::info() << "[wgpu] present mode = " << pm_name << note;
|
||||||
|
}
|
||||||
|
cfg.alphaMode = WGPUCompositeAlphaMode_Auto;
|
||||||
|
|
||||||
|
wgpuSurfaceConfigure(surface_, &cfg);
|
||||||
|
configured_w_ = width_px;
|
||||||
|
configured_h_ = height_px;
|
||||||
|
surface_configured_ = true;
|
||||||
|
ensureDepthTexture(width_px, height_px);
|
||||||
|
ensureMsaaColorTexture(width_px, height_px);
|
||||||
|
ensureHizTextures(width_px, height_px);
|
||||||
|
// depth_view_ was just replaced; force the HiZ + edge bind groups
|
||||||
|
// to rebuild against the new view on next encode.
|
||||||
|
if (hiz_bind_group_) {
|
||||||
|
wgpuBindGroupRelease(hiz_bind_group_);
|
||||||
|
hiz_bind_group_ = nullptr;
|
||||||
|
}
|
||||||
|
if (edge_bind_group_) {
|
||||||
|
wgpuBindGroupRelease(edge_bind_group_);
|
||||||
|
edge_bind_group_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -323,6 +323,16 @@ public:
|
|||||||
void uploadInstanceChunk(const InstanceChunk& chunk);
|
void uploadInstanceChunk(const InstanceChunk& chunk);
|
||||||
void finalizeModel(std::uint32_t model_id);
|
void finalizeModel(std::uint32_t model_id);
|
||||||
|
|
||||||
|
// ---- Surface configuration (#84-u) ------------------------------------
|
||||||
|
//
|
||||||
|
// Configure the swapchain at the given physical size. Picks a present
|
||||||
|
// mode from caps + preference order (Mailbox → Immediate → FifoRelaxed
|
||||||
|
// → Fifo, overridable via WGPU_PRESENT_MODE), reconfigures the
|
||||||
|
// surface, then (re)allocates the depth + MSAA color + HiZ resolve
|
||||||
|
// textures and invalidates the HiZ + edge bind groups so they
|
||||||
|
// rebuild against the new depth view on next encode.
|
||||||
|
void configureSurface(int width_px, int height_px);
|
||||||
|
|
||||||
// ---- HiZ + framebuffer attachments (#84-r) ----------------------------
|
// ---- HiZ + framebuffer attachments (#84-r) ----------------------------
|
||||||
//
|
//
|
||||||
// Build the HiZ resolve pipeline (and shader module + BGL + uniform
|
// Build the HiZ resolve pipeline (and shader module + BGL + uniform
|
||||||
|
|||||||
@@ -612,7 +612,7 @@ void ViewportWindow::exposeEvent(QExposeEvent* /*event*/) {
|
|||||||
const int w = int(width() * devicePixelRatio());
|
const int w = int(width() * devicePixelRatio());
|
||||||
const int h = int(height() * devicePixelRatio());
|
const int h = int(height() * devicePixelRatio());
|
||||||
if (w > 0 && h > 0 && (w != configured_w_ || h != configured_h_)) {
|
if (w > 0 && h > 0 && (w != configured_w_ || h != configured_h_)) {
|
||||||
configureSurface(w, h);
|
core_.configureSurface(w, h);
|
||||||
}
|
}
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
}
|
}
|
||||||
@@ -622,7 +622,7 @@ void ViewportWindow::resizeEvent(QResizeEvent* /*event*/) {
|
|||||||
const int w = int(width() * devicePixelRatio());
|
const int w = int(width() * devicePixelRatio());
|
||||||
const int h = int(height() * devicePixelRatio());
|
const int h = int(height() * devicePixelRatio());
|
||||||
if (w > 0 && h > 0) {
|
if (w > 0 && h > 0) {
|
||||||
configureSurface(w, h);
|
core_.configureSurface(w, h);
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -755,163 +755,7 @@ bool ViewportWindow::initWgpu() {
|
|||||||
// Surface (re)configure + render
|
// Surface (re)configure + render
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
void ViewportWindow::configureSurface(int width_px, int height_px) {
|
// configureSurface moved to ViewportCore (#84-u).
|
||||||
WGPUSurfaceConfiguration cfg = {};
|
|
||||||
cfg.device = device_;
|
|
||||||
cfg.format = surface_format_;
|
|
||||||
// CopySrc lets captureNextFrameToPng copy the surface texture back to
|
|
||||||
// host memory. Trivial cost on all known backends.
|
|
||||||
cfg.usage = WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_CopySrc;
|
|
||||||
cfg.width = uint32_t(width_px);
|
|
||||||
cfg.height = uint32_t(height_px);
|
|
||||||
// Present mode. WGPU_PRESENT_MODE=fifo|fifo_relaxed|mailbox|immediate
|
|
||||||
// overrides; default is mailbox.
|
|
||||||
// mailbox — DEFAULT. Vsync-aligned (no tearing), one-frame
|
|
||||||
// queue (last-frame-wins). ~16ms input→display
|
|
||||||
// latency. wgpu-native falls back to fifo
|
|
||||||
// automatically on backends that don't implement
|
|
||||||
// it (Vulkan + NVIDIA on Linux is the historical
|
|
||||||
// one).
|
|
||||||
// fifo — strict vsync, 2–3 frame DXGI/swapchain queue
|
|
||||||
// (~50ms latency). Most power-efficient, but
|
|
||||||
// visibly laggy for cursor-bound interactions
|
|
||||||
// (marquee, pivot, orbit). Was the stage-1 default
|
|
||||||
// because Fifo is the only mode the WebGPU spec
|
|
||||||
// *requires* backends to support.
|
|
||||||
// fifo_relaxed — adaptive vsync. Tears when a frame misses the
|
|
||||||
// refresh deadline, smooth otherwise. Useful in
|
|
||||||
// the fly-mode-jitter case where Fifo would
|
|
||||||
// double-frame on a missed vsync.
|
|
||||||
// immediate — no vsync at all. Frames presented as soon as
|
|
||||||
// ready, may tear on motion. Useful for raw
|
|
||||||
// throughput benchmarking.
|
|
||||||
// Preference order. Override with WGPU_PRESENT_MODE=...; otherwise we
|
|
||||||
// try Mailbox → Immediate → FifoRelaxed → Fifo and pick the first
|
|
||||||
// mode actually advertised by the surface. Asking for a mode that
|
|
||||||
// the backend doesn't list aborts the process (wgpu-native panics
|
|
||||||
// from Rust at wgpuSurfaceConfigure). On Metal in particular only
|
|
||||||
// Fifo + Immediate are exposed today, so a static Mailbox default
|
|
||||||
// crashes there.
|
|
||||||
//
|
|
||||||
// Why Immediate sits above FifoRelaxed: Mailbox is the right answer
|
|
||||||
// for an interactive viewer (vsync-aligned, no tearing, 1-frame
|
|
||||||
// queue) but a meaningful subset of Linux Vulkan stacks (some
|
|
||||||
// compositors, some driver/WSI combinations) silently don't expose
|
|
||||||
// it — see the "[wgpu] surface advertises present modes:" startup
|
|
||||||
// log. On those stacks, Fifo's 2-3 frame queue doubles input-to-
|
|
||||||
// photon latency the moment WASD activates, which on a 60 Hz
|
|
||||||
// display reads as judder during fly-mode mouse-look. Immediate
|
|
||||||
// can tear but keeps latency at one render-body, which preserves
|
|
||||||
// the responsive-feel that's the main reason to use a wgpu viewer.
|
|
||||||
// FifoRelaxed is the middle option — better latency than Fifo at
|
|
||||||
// the edge, can tear when over budget — kept as the next fallback.
|
|
||||||
WGPUPresentMode preferred[4] = {
|
|
||||||
WGPUPresentMode_Mailbox,
|
|
||||||
WGPUPresentMode_Immediate,
|
|
||||||
WGPUPresentMode_FifoRelaxed,
|
|
||||||
WGPUPresentMode_Fifo,
|
|
||||||
};
|
|
||||||
const char* pm_name = "mailbox";
|
|
||||||
if (const char* s = std::getenv("WGPU_PRESENT_MODE")) {
|
|
||||||
WGPUPresentMode override_pm = WGPUPresentMode_Fifo;
|
|
||||||
bool known = true;
|
|
||||||
if (std::strcmp(s, "fifo") == 0) { override_pm = WGPUPresentMode_Fifo; pm_name = "fifo"; }
|
|
||||||
else if (std::strcmp(s, "fifo_relaxed") == 0) { override_pm = WGPUPresentMode_FifoRelaxed; pm_name = "fifo_relaxed"; }
|
|
||||||
else if (std::strcmp(s, "mailbox") == 0) { override_pm = WGPUPresentMode_Mailbox; pm_name = "mailbox"; }
|
|
||||||
else if (std::strcmp(s, "immediate") == 0) { override_pm = WGPUPresentMode_Immediate; pm_name = "immediate"; }
|
|
||||||
else {
|
|
||||||
known = false;
|
|
||||||
Log::warn().noquote().nospace()
|
|
||||||
<< "[wgpu] unknown WGPU_PRESENT_MODE=" << s
|
|
||||||
<< " (expected fifo|fifo_relaxed|mailbox|immediate); falling back to preference order";
|
|
||||||
}
|
|
||||||
if (known) {
|
|
||||||
preferred[0] = override_pm;
|
|
||||||
preferred[1] = WGPUPresentMode_Fifo; // Fifo is the only guaranteed-supported mode
|
|
||||||
preferred[2] = preferred[3] = WGPUPresentMode_Fifo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WGPUSurfaceCapabilities caps = {};
|
|
||||||
wgpuSurfaceGetCapabilities(surface_, adapter_, &caps);
|
|
||||||
auto supports = [&](WGPUPresentMode mode) {
|
|
||||||
for (size_t i = 0; i < caps.presentModeCount; ++i) {
|
|
||||||
if (caps.presentModes[i] == mode) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Log the full advertised set on first configure. Diagnostic for
|
|
||||||
// "WGPU_PRESENT_MODE=mailbox falls back to fifo" — if Mailbox is
|
|
||||||
// missing here, the driver/compositor doesn't expose it (drives the
|
|
||||||
// input-latency question; see WGPU_PRESENT_MODE notes above). If
|
|
||||||
// Mailbox is listed but we still pick Fifo, the preference order
|
|
||||||
// has a bug.
|
|
||||||
if (!surface_configured_) {
|
|
||||||
QString advertised;
|
|
||||||
for (size_t i = 0; i < caps.presentModeCount; ++i) {
|
|
||||||
const char* name = "?";
|
|
||||||
switch (caps.presentModes[i]) {
|
|
||||||
case WGPUPresentMode_Fifo: name = "fifo"; break;
|
|
||||||
case WGPUPresentMode_FifoRelaxed: name = "fifo_relaxed"; break;
|
|
||||||
case WGPUPresentMode_Mailbox: name = "mailbox"; break;
|
|
||||||
case WGPUPresentMode_Immediate: name = "immediate"; break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
if (i > 0) advertised += ", ";
|
|
||||||
advertised += QString::fromLatin1(name);
|
|
||||||
}
|
|
||||||
Log::info().noquote().nospace()
|
|
||||||
<< "[wgpu] surface advertises present modes: " << advertised;
|
|
||||||
}
|
|
||||||
WGPUPresentMode pm = WGPUPresentMode_Fifo; // spec-guaranteed fallback
|
|
||||||
for (WGPUPresentMode candidate : preferred) {
|
|
||||||
if (supports(candidate)) { pm = candidate; break; }
|
|
||||||
}
|
|
||||||
switch (pm) {
|
|
||||||
case WGPUPresentMode_Mailbox: pm_name = "mailbox"; break;
|
|
||||||
case WGPUPresentMode_FifoRelaxed: pm_name = "fifo_relaxed"; break;
|
|
||||||
case WGPUPresentMode_Immediate: pm_name = "immediate"; break;
|
|
||||||
case WGPUPresentMode_Fifo: pm_name = "fifo"; break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
wgpuSurfaceCapabilitiesFreeMembers(caps);
|
|
||||||
cfg.presentMode = pm;
|
|
||||||
if (!surface_configured_) {
|
|
||||||
const char* note = "";
|
|
||||||
switch (pm) {
|
|
||||||
case WGPUPresentMode_Mailbox:
|
|
||||||
note = " (vsync-aligned, no queue lag — default)"; break;
|
|
||||||
case WGPUPresentMode_Fifo:
|
|
||||||
note = " (strict vsync, may queue 2–3 frames)"; break;
|
|
||||||
case WGPUPresentMode_FifoRelaxed:
|
|
||||||
note = " (adaptive vsync — sync if in budget, tear if not)"; break;
|
|
||||||
case WGPUPresentMode_Immediate:
|
|
||||||
note = " (vsync OFF — framerate uncapped, may tear)"; break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
Log::info().noquote().nospace() << "[wgpu] present mode = " << pm_name << note;
|
|
||||||
}
|
|
||||||
cfg.alphaMode = WGPUCompositeAlphaMode_Auto;
|
|
||||||
|
|
||||||
wgpuSurfaceConfigure(surface_, &cfg);
|
|
||||||
configured_w_ = width_px;
|
|
||||||
configured_h_ = height_px;
|
|
||||||
surface_configured_ = true;
|
|
||||||
core_.ensureDepthTexture(width_px, height_px);
|
|
||||||
core_.ensureMsaaColorTexture(width_px, height_px);
|
|
||||||
core_.ensureHizTextures(width_px, height_px);
|
|
||||||
// depth_view_ was just replaced; force the HiZ + edge bind groups to
|
|
||||||
// rebuild against the new view on next encode.
|
|
||||||
if (hiz_bind_group_) {
|
|
||||||
wgpuBindGroupRelease(hiz_bind_group_);
|
|
||||||
hiz_bind_group_ = nullptr;
|
|
||||||
}
|
|
||||||
if (edge_bind_group_) {
|
|
||||||
wgpuBindGroupRelease(edge_bind_group_);
|
|
||||||
edge_bind_group_ = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// CPU frustum cull + per-mesh compaction
|
// CPU frustum cull + per-mesh compaction
|
||||||
@@ -1562,7 +1406,7 @@ void ViewportWindow::render() {
|
|||||||
// Reconfigure and try again next frame.
|
// Reconfigure and try again next frame.
|
||||||
const int w = int(width() * devicePixelRatio());
|
const int w = int(width() * devicePixelRatio());
|
||||||
const int h = int(height() * devicePixelRatio());
|
const int h = int(height() * devicePixelRatio());
|
||||||
if (w > 0 && h > 0) configureSurface(w, h);
|
if (w > 0 && h > 0) core_.configureSurface(w, h);
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ protected:
|
|||||||
private:
|
private:
|
||||||
bool initWgpu();
|
bool initWgpu();
|
||||||
bool createSurface();
|
bool createSurface();
|
||||||
void configureSurface(int width_px, int height_px);
|
// configureSurface moved to ViewportCore (#84-u).
|
||||||
void render();
|
void render();
|
||||||
void shutdown();
|
void shutdown();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user