fix mirror seam: single-pass rendering with CPU-side vertex expansion

Instead of 4 MVP passes that double-draw at mirror boundaries (causing
bright seam from overlapping semi-transparent bars), expand the half-size
vertices into all 4 quadrants on the CPU by flipping coordinates. Draw
everything in a single pass with one full-screen ortho MVP. Each pixel is
now covered by exactly one quadrant's geometry — no overlap, no seam.

Also simplifies render: removes 4-pass loop, removes separate cepstrum
MVP slot (shares slot 0).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
pszsh 2026-02-27 02:06:05 -08:00
parent 840fe5a11f
commit d78c85d4ad
1 changed files with 56 additions and 56 deletions

View File

@ -390,7 +390,43 @@ void VisualizerWidget::render(QRhiCommandBuffer *cb) {
m_lastBuildW = w;
m_lastBuildH = h;
if (m_mirrored) {
buildVertices(w * 0.55f, h / 2 + 2);
buildVertices(w * 0.55f, h / 2);
// Expand half-size vertices into all 4 quadrants (eliminates mirror seam)
{
int fillFloats = m_fillVertexCount * 6;
int lineFloats = m_lineVertexCount * 6;
float fw = (float)w, fh = (float)h;
auto expand = [&](const float *src, int nVerts, std::vector<float> &dst) {
for (int q = 0; q < 4; q++) {
bool fx = (q == 1 || q == 3);
bool fy = (q == 2 || q == 3);
for (int v = 0; v < nVerts; v++) {
int b = v * 6;
dst.push_back(fx ? fw - src[b] : src[b]);
dst.push_back(fy ? fh - src[b + 1] : src[b + 1]);
dst.push_back(src[b + 2]);
dst.push_back(src[b + 3]);
dst.push_back(src[b + 4]);
dst.push_back(src[b + 5]);
}
}
};
std::vector<float> expFill, expLine;
expFill.reserve(fillFloats * 4);
expLine.reserve(lineFloats * 4);
expand(m_vertices.data(), m_fillVertexCount, expFill);
expand(m_vertices.data() + fillFloats, m_lineVertexCount, expLine);
m_vertices.clear();
m_vertices.insert(m_vertices.end(), expFill.begin(), expFill.end());
m_vertices.insert(m_vertices.end(), expLine.begin(), expLine.end());
m_fillVertexCount *= 4;
m_lineVertexCount *= 4;
}
buildCepstrumVertices(w, h);
} else {
buildVertices(w, h);
@ -398,8 +434,6 @@ void VisualizerWidget::render(QRhiCommandBuffer *cb) {
}
}
int numPasses = m_mirrored ? 4 : 1;
// Prepare resource updates
QRhiResourceUpdateBatch *u = m_rhi->nextResourceUpdateBatch();
@ -420,43 +454,13 @@ void VisualizerWidget::render(QRhiCommandBuffer *cb) {
}
}
// Upload MVP matrices
// Upload single full-screen ortho MVP (slot 0)
QMatrix4x4 correction = m_rhi->clipSpaceCorrMatrix();
for (int i = 0; i < numPasses; i++) {
{
QMatrix4x4 proj;
proj.ortho(0, (float)w, (float)h, 0, -1, 1);
if (m_mirrored) {
switch (i) {
case 0: break;
case 1:
proj.translate(w, 0, 0);
proj.scale(-1, 1, 1);
break;
case 2:
proj.translate(0, h, 0);
proj.scale(1, -1, 1);
break;
case 3:
proj.translate(w, h, 0);
proj.scale(-1, -1, 1);
break;
}
}
QMatrix4x4 mvp = correction * proj;
u->updateDynamicBuffer(m_ubuf.get(), i * m_ubufAlign, 64,
mvp.constData());
}
// Upload full-screen ortho MVP for cepstrum (slot 4)
if (m_mirrored && m_cepstrumVertexCount > 0) {
QMatrix4x4 cepProj;
cepProj.ortho(0, (float)w, (float)h, 0, -1, 1);
QMatrix4x4 cepMvp = correction * cepProj;
u->updateDynamicBuffer(m_ubuf.get(), 4 * m_ubufAlign, 64,
cepMvp.constData());
u->updateDynamicBuffer(m_ubuf.get(), 0, 64, mvp.constData());
}
// Begin render pass
@ -465,9 +469,7 @@ void VisualizerWidget::render(QRhiCommandBuffer *cb) {
(float)outputSize.height()});
const QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf.get(), 0);
for (int i = 0; i < numPasses; i++) {
QRhiCommandBuffer::DynamicOffset dynOfs(0, quint32(i * m_ubufAlign));
QRhiCommandBuffer::DynamicOffset dynOfs(0, 0);
if (m_fillVertexCount > 0) {
cb->setGraphicsPipeline(m_fillPipeline.get());
@ -482,13 +484,11 @@ void VisualizerWidget::render(QRhiCommandBuffer *cb) {
cb->setVertexInput(0, 1, &vbufBinding);
cb->draw(m_lineVertexCount, 1, m_fillVertexCount, 0);
}
}
// --- Cepstral Thread (single full-screen pass, after mirror loop) ---
// --- Cepstral Thread ---
if (m_mirrored && m_cepstrumVertexCount > 0) {
QRhiCommandBuffer::DynamicOffset cepOfs(0, quint32(4 * m_ubufAlign));
cb->setGraphicsPipeline(m_linePipeline.get());
cb->setShaderResources(m_srb.get(), 1, &cepOfs);
cb->setShaderResources(m_srb.get(), 1, &dynOfs);
cb->setVertexInput(0, 1, &vbufBinding);
cb->draw(m_cepstrumVertexCount, 1, m_fillVertexCount + m_lineVertexCount, 0);
}