MSAA: Only multisample full size framebuffers

Smaller framebuffers are mostly effects (blur, bloom), and MSAA changes
how those come out, like a higher resolution would. Syphon Filter:
Logan's Shadow grew a glow around its lit floor panels that the PSP
doesn't have. Effects in Call of Duty: Road to Victory and Assassin's
Creed: Bloodlines shifted too.

A framebuffer that grows to full size gets MSAA from then on. The
temporary buffer that shifts depth for offset rendering now takes the
samples of the framebuffer it shifts, since they can differ.

Over the frame dumps at 2x and 4x, the differences away from edges
compared to no MSAA drop by a fifth and 53 dumps get closer. The one
that moved away, Silent Hill: Origins' flashlight shadow, now matches
the PSP; without MSAA it shows a known replay artifact there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-18 12:37:02 +02:00
1 parent 2c19007b61
commit d4cc5d8666
2 files changed
+19 -7

No files matched your search

+14 -6
View File
@@ -1078,7 +1078,7 @@ void FramebufferManagerCommon::ShiftDepthForOffsetRendering(VirtualFramebuffer *
if (w <= 0) {
return;
}
Draw::Framebuffer *temp = GetTempFBO(TempFBO::DEPTH_SHIFT, vfb->renderWidth, vfb->renderHeight);
Draw::Framebuffer *temp = GetTempFBO(TempFBO::DEPTH_SHIFT, vfb->renderWidth, vfb->renderHeight, vfb->fbo->MultiSampleLevel());
if (!temp) {
return;
}
@@ -1984,6 +1984,7 @@ void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, int w,
}
bool creating = old.bufferWidth == 0;
vfb->multisampled = ShouldMultisample(vfb);
if (creating) {
INFO_LOG(Log::FrameBuf, "Creating %s FBO at %08x/%08x stride=%d %dx%d (force=%d)", GeBufferFormatToString(vfb->fb_format), vfb->fb_address, vfb->z_address, vfb->fb_stride, vfb->bufferWidth, vfb->bufferHeight, (int)force);
} else {
@@ -2016,7 +2017,7 @@ void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, int w,
gpuStats.perFrame.numFBOsCreated++;
vfb->fbo = draw_->CreateFramebuffer({ vfb->renderWidth, vfb->renderHeight, 1, GetFramebufferLayers(), msaaLevel_, true, tag });
vfb->fbo = draw_->CreateFramebuffer({ vfb->renderWidth, vfb->renderHeight, 1, GetFramebufferLayers(), vfb->multisampled ? msaaLevel_ : 0, true, tag });
if (Memory::IsVRAMAddress(vfb->fb_address) && vfb->fb_stride != 0) {
NotifyMemInfo(MemBlockFlags::ALLOC, vfb->fb_address, vfb->BufferByteSize(RASTER_COLOR), tag, len);
}
@@ -3165,6 +3166,12 @@ static const char *TempFBOReasonToString(TempFBO reason) {
return "";
}
// Only full size framebuffers get MSAA. Smaller ones are mostly effects (blur, bloom), and MSAA changes those
// (Syphon Filter: Logan's Shadow grows a glow around lit floor panels).
bool FramebufferManagerCommon::ShouldMultisample(const VirtualFramebuffer *vfb) const {
return vfb->bufferWidth >= 480 && vfb->bufferHeight >= 272;
}
Draw::Framebuffer *FramebufferManagerCommon::CopyColorSample0(VirtualFramebuffer *vfb) {
if (!vfb->fbo || vfb->fbo->MultiSampleLevel() == 0) {
return nullptr;
@@ -3186,8 +3193,11 @@ Draw::Framebuffer *FramebufferManagerCommon::CopyColorSample0(VirtualFramebuffer
return copy;
}
Draw::Framebuffer *FramebufferManagerCommon::GetTempFBO(TempFBO reason, u16 w, u16 h) {
u64 key = ((u64)reason << 48) | ((u32)w << 16) | h;
Draw::Framebuffer *FramebufferManagerCommon::GetTempFBO(TempFBO reason, u16 w, u16 h, int msaaLevel) {
if (reason != TempFBO::DEPTH_SHIFT) {
msaaLevel = 0;
}
u64 key = ((u64)msaaLevel << 56) | ((u64)reason << 48) | ((u32)w << 16) | h;
auto it = tempFBOs_.find(key);
if (it != tempFBOs_.end()) {
it->second.last_frame_used = gpuStats.totals.numFlips;
@@ -3195,8 +3205,6 @@ Draw::Framebuffer *FramebufferManagerCommon::GetTempFBO(TempFBO reason, u16 w, u
}
bool z_stencil = reason == TempFBO::STENCIL || reason == TempFBO::DEPTH_SHIFT;
// The depth shift copies to and from a framebuffer, which needs the same samples.
const int msaaLevel = reason == TempFBO::DEPTH_SHIFT ? msaaLevel_ : 0;
char name[128];
snprintf(name, sizeof(name), "tempfbo_%s_%dx%d", TempFBOReasonToString(reason), w / renderScaleFactor_, h / renderScaleFactor_);