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:
parent
840fe5a11f
commit
d78c85d4ad
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue