Make un-buffered rendering much smarter, removing flicker.

This turns it into a very viable option for many games. You do lose some FX
but it can as a result even be used as a workaround for the massive glow
in Wipeout...
This commit is contained in:
Henrik Rydgard 2013-03-03 13:00:21 +01:00
parent fa11e4971b
commit bc15617392
17 changed files with 149 additions and 64 deletions

View File

@ -1085,8 +1085,8 @@ void ARMXEmitter::VMOV(ARMReg Dest, ARMReg Src)
void ARMXEmitter::VCVT(ARMReg Sd, ARMReg Sm, int flags)
{
bool op = (flags & TO_INT) ? (flags & ROUND_TO_ZERO) : (flags & IS_SIGNED);
bool op2 = (flags & TO_INT) ? (flags & IS_SIGNED) : 0;
int op = ((flags & TO_INT) ? (flags & ROUND_TO_ZERO) : (flags & IS_SIGNED)) ? 1 : 0;
int op2 = ((flags & TO_INT) ? (flags & IS_SIGNED) : 0) ? 1 : 0;
Sd = SubBase(Sd);
Sm = SubBase(Sm);

View File

@ -210,7 +210,7 @@ void CalculateFPS()
}
char stats[50];
sprintf(stats, "%0.1f", fps);
sprintf(stats, "VPS: %0.1f", fps);
#ifdef USING_GLES2
float zoom = 0.7f; /// g_Config.iWindowZoom;
@ -285,13 +285,12 @@ void DebugStats()
}
// Let's collect all the throttling and frameskipping logic here.
void DoFrameTiming(bool &throttle, bool &skipFrame, bool &skipFlip) {
void DoFrameTiming(bool &throttle, bool &skipFrame) {
#ifdef _WIN32
throttle = !GetAsyncKeyState(VK_TAB);
#else
throttle = false;
throttle = true;
#endif
skipFlip = false;
skipFrame = false;
if (PSP_CoreParameter().headLess)
throttle = false;
@ -311,7 +310,6 @@ void DoFrameTiming(bool &throttle, bool &skipFrame, bool &skipFlip) {
if (curFrameTime > nextFrameTime && doFrameSkip) {
// Argh, we are falling behind! Let's skip a frame and see if we catch up.
skipFrame = true;
skipFlip = true;
INFO_LOG(HLE,"FRAMESKIP %i", numSkippedFrames);
}
@ -339,10 +337,10 @@ void DoFrameTiming(bool &throttle, bool &skipFrame, bool &skipFlip) {
nextFrameTime = nextFrameTime + 1.0 / 60.0;
}
// Max 6 skipped frames in a row - 10 fps is really the bare minimum for playability.
if (numSkippedFrames >= 4) {
// Max 4 skipped frames in a row - 15 fps is really the bare minimum for playability.
// We check for 3 here so it's 3 skipped frames, 1 non skipped, 3 skipped, etc.
if (numSkippedFrames >= 3) {
skipFrame = false;
skipFlip = false;
}
}
@ -389,17 +387,23 @@ void hleEnterVblank(u64 userdata, int cyclesLate) {
CalculateFPS();
}
bool skipFlip = false;
// This frame was skipped, so no need to flip.
if (gstate_c.skipDrawReason & SKIPDRAW_SKIPFRAME) {
skipFlip = true;
}
// Draw screen overlays before blitting. Saves and restores the Ge context.
// Yeah, this has to be the right moment to end the frame. Give the graphics backend opportunity
// to blit the framebuffer, in order to support half-framerate games that otherwise wouldn't have
// anything to draw here.
gstate_c.skipDrawReason &= ~SKIPDRAW_SKIPFRAME;
bool throttle, skipFrame, skipFlip;
bool throttle, skipFrame;
DoFrameTiming(throttle, skipFrame, skipFlip);
DoFrameTiming(throttle, skipFrame);
// Setting CORE_NEXTFRAME causes a swap.
if (skipFrame) {
gstate_c.skipDrawReason |= SKIPDRAW_SKIPFRAME;
numSkippedFrames++;
@ -408,11 +412,11 @@ void hleEnterVblank(u64 userdata, int cyclesLate) {
}
if (!skipFlip) {
// Might've just quit / been paused.
if (coreState == CORE_RUNNING) {
// Setting CORE_NEXTFRAME causes a swap.
// Check first though, might've just quit / been paused.
if (coreState == CORE_RUNNING && gpu->FramebufferDirty()) {
coreState = CORE_NEXTFRAME;
}
CoreTiming::ScheduleEvent(0 - cyclesLate, afterFlipEvent, 0);
gpu->CopyDisplayToOutput();
}
@ -420,6 +424,7 @@ void hleEnterVblank(u64 userdata, int cyclesLate) {
// Returning here with coreState == CORE_NEXTFRAME causes a buffer flip to happen (next frame).
// Right after, we regain control for a little bit in hleAfterFlip. I think that's a great
// place to do housekeeping.
CoreTiming::ScheduleEvent(0 - cyclesLate, afterFlipEvent, 0);
}
void hleAfterFlip(u64 userdata, int cyclesLate)

View File

@ -355,7 +355,7 @@ void ArmJitBlockCache::DestroyBlock(int block_num, bool invalidate)
// checkedEntry is the only "linked" entrance so it's enough to overwrite that.
ARMXEmitter emit((u8 *)b.checkedEntry);
emit.MOVI2R(R0, b.originalAddress);
emit.STR(R10, R0, offsetof(MIPSState, pc));
emit.STR(CTXREG, R0, offsetof(MIPSState, pc));
emit.B(MIPSComp::jit->dispatcher);
emit.FlushIcache();
}

View File

@ -251,15 +251,23 @@ void GLES_GPU::SetDisplayFramebuffer(u32 framebuf, u32 stride, int format) {
framebufferManager_.SetDisplayFramebuffer(framebuf, stride, format);
}
bool GLES_GPU::FramebufferDirty() {
if (!g_Config.bBufferedRendering) {
VirtualFramebuffer *vfb = framebufferManager_.GetDisplayFBO();
if (vfb)
return vfb->dirtyAfterDisplay;
}
return true;
}
void GLES_GPU::CopyDisplayToOutput() {
glstate.colorMask.set(true, true, true, true);
transformDraw_.Flush();
if (!g_Config.bBufferedRendering)
return;
EndDebugDraw();
framebufferManager_.CopyDisplayToOutput();
framebufferManager_.EndFrame();
shaderManager_->DirtyShader();
shaderManager_->DirtyUniform(DIRTY_ALL);
@ -313,10 +321,16 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) {
case GE_CMD_PRIM:
{
if (gstate_c.skipDrawReason)
return;
// This drives all drawing. All other state we just buffer up, then we apply it only
// when it's time to draw. As most PSP games set state redundantly ALL THE TIME, this is a huge optimization.
// This also make skipping drawing very effective.
if (gstate_c.skipDrawReason & SKIPDRAW_SKIPFRAME)
return;
framebufferManager_.SetRenderFrameBuffer();
if (gstate_c.skipDrawReason & SKIPDRAW_NON_DISPLAYED_FB)
return;
u32 count = data & 0xFFFF;
u32 type = data >> 16;
@ -650,6 +664,7 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) {
{
// TODO: Here we should check if the transfer overlaps a framebuffer or any textures,
// and take appropriate action. This is a block transfer between RAM and VRAM, or vice versa.
// Can we skip this on SkipDraw?
DoBlockTransfer();
break;
}

View File

@ -63,6 +63,7 @@ public:
{
return textureCache_.DecodeTexture(dest, state);
}
virtual bool FramebufferDirty();
std::vector<FramebufferInfo> GetFramebufferList();

View File

@ -88,7 +88,7 @@ void GenerateFragmentShader(char *buffer)
bool enableFog = gstate.isFogEnabled() && !gstate.isModeThrough() && !gstate.isModeClear();
bool enableAlphaTest = (gstate.alphaTestEnable & 1) && !gstate.isModeClear();
bool enableColorTest = (gstate.colorTestEnable & 1) && !gstate.isModeClear();
bool enableColorDoubling = gstate.texfunc & 0x10000;
bool enableColorDoubling = (gstate.texfunc & 0x10000) != 0;
if (doTexture)

View File

@ -266,8 +266,6 @@ void GetViewportDimensions(int *w, int *h) {
}
void FramebufferManager::SetRenderFrameBuffer() {
if (!g_Config.bBufferedRendering)
return;
// Get parameters
u32 fb_address = (gstate.fbptr & 0xFFE000) | ((gstate.fbwidth & 0xFF0000) << 8);
int fb_stride = gstate.fbwidth & 0x3C0;
@ -328,6 +326,7 @@ void FramebufferManager::SetRenderFrameBuffer() {
vfb->renderHeight = (u16)(drawing_height * renderHeightFactor);
vfb->format = fmt;
vfb->usageFlags = FB_USAGE_RENDERTARGET;
vfb->dirtyAfterDisplay = true;
switch (fmt) {
case GE_FORMAT_4444: vfb->colorDepth = FBO_4444; break;
@ -344,13 +343,26 @@ void FramebufferManager::SetRenderFrameBuffer() {
// vfb->colorDepth = FBO_8888;
//#endif
vfb->fbo = fbo_create(vfb->renderWidth, vfb->renderHeight, 1, true, vfb->colorDepth);
if (g_Config.bBufferedRendering)
{
vfb->fbo = fbo_create(vfb->renderWidth, vfb->renderHeight, 1, true, vfb->colorDepth);
}
else
vfb->fbo = 0;
textureCache_->NotifyFramebuffer(vfb->fb_address, vfb);
vfb->last_frame_used = gpuStats.numFrames;
vfbs_.push_back(vfb);
fbo_bind_as_render_target(vfb->fbo);
if (g_Config.bBufferedRendering) {
fbo_bind_as_render_target(vfb->fbo);
} else {
fbo_unbind();
// Let's ignore rendering to targets that have not (yet) been displayed.
gstate_c.skipDrawReason |= SKIPDRAW_NON_DISPLAYED_FB;
}
glEnable(GL_DITHER);
glstate.viewport.set(0, 0, vfb->renderWidth, vfb->renderHeight);
currentRenderVfb_ = vfb;
@ -359,21 +371,39 @@ void FramebufferManager::SetRenderFrameBuffer() {
glClearColor(0,0,0,1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
INFO_LOG(HLE, "Creating FBO for %08x : %i x %i x %i", vfb->fb_address, vfb->width, vfb->height, vfb->format);
return;
}
if (vfb != currentRenderVfb_) {
// We already have it!
} else if (vfb != currentRenderVfb_) {
// Use it as a render target.
DEBUG_LOG(HLE, "Switching render target to FBO for %08x", vfb->fb_address);
vfb->usageFlags |= FB_USAGE_RENDERTARGET;
gstate_c.textureChanged = true;
if (vfb->last_frame_used != gpuStats.numFrames) {
// Android optimization
//glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
vfb->last_frame_used = gpuStats.numFrames;
fbo_bind_as_render_target(vfb->fbo);
vfb->dirtyAfterDisplay = true;
if (g_Config.bBufferedRendering && vfb->fbo) {
fbo_bind_as_render_target(vfb->fbo);
} else {
fbo_unbind();
// Let's ignore rendering to targets that have not (yet) been displayed.
if (vfb->usageFlags & FB_USAGE_DISPLAYED_FRAMEBUFFER)
gstate_c.skipDrawReason &= ~SKIPDRAW_NON_DISPLAYED_FB;
else
gstate_c.skipDrawReason |= SKIPDRAW_NON_DISPLAYED_FB;
/*
if (drawing_width == 480 && drawing_height == 272) {
gstate_c.skipDrawReason &= ~SKIPDRAW_SKIPNONFB;
// OK!
} else {
gstate_c.skipDrawReason |= ~SKIPDRAW_SKIPNONFB;
}*/
}
textureCache_->NotifyFramebuffer(vfb->fb_address, vfb);
#ifdef USING_GLES2
@ -394,7 +424,6 @@ void FramebufferManager::SetRenderFrameBuffer() {
}
}
void FramebufferManager::CopyDisplayToOutput() {
fbo_unbind();
@ -409,22 +438,31 @@ void FramebufferManager::CopyDisplayToOutput() {
return;
}
vfb->usageFlags |= FB_USAGE_DISPLAYED_FRAMEBUFFER;
vfb->dirtyAfterDisplay = false;
prevPrevDisplayFramebuf_ = prevDisplayFramebuf_;
prevDisplayFramebuf_ = displayFramebuf_;
displayFramebuf_ = vfb;
glstate.viewport.set(0, 0, PSP_CoreParameter().pixelWidth, PSP_CoreParameter().pixelHeight);
currentRenderVfb_ = 0;
DEBUG_LOG(HLE, "Displaying FBO %08x", vfb->fb_address);
glstate.blend.disable();
glstate.cullFace.disable();
glstate.depthTest.disable();
glstate.scissorTest.disable();
glstate.stencilTest.disable();
if (vfb->fbo) {
glstate.viewport.set(0, 0, PSP_CoreParameter().pixelWidth, PSP_CoreParameter().pixelHeight);
DEBUG_LOG(HLE, "Displaying FBO %08x", vfb->fb_address);
glstate.blend.disable();
glstate.cullFace.disable();
glstate.depthTest.disable();
glstate.scissorTest.disable();
glstate.stencilTest.disable();
fbo_bind_color_as_texture(vfb->fbo, 0);
fbo_bind_color_as_texture(vfb->fbo, 0);
// These are in the output display coordinates
float x, y, w, h;
CenterRect(&x, &y, &w, &h, 480.0f, 272.0f, (float)PSP_CoreParameter().pixelWidth, (float)PSP_CoreParameter().pixelHeight);
DrawActiveTexture(x, y, w, h, true);
}
if (resized_) {
glstate.depthWrite.set(GL_TRUE);
@ -432,11 +470,9 @@ void FramebufferManager::CopyDisplayToOutput() {
glClearColor(0,0,0,1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
// These are in the output display coordinates
float x, y, w, h;
CenterRect(&x, &y, &w, &h, 480.0f, 272.0f, (float)PSP_CoreParameter().pixelWidth, (float)PSP_CoreParameter().pixelHeight);
DrawActiveTexture(x, y, w, h, true);
}
void FramebufferManager::EndFrame() {
if (resized_) {
DestroyAllFBOs();
glstate.viewport.set(0, 0, PSP_CoreParameter().pixelWidth, PSP_CoreParameter().pixelHeight);
@ -472,8 +508,7 @@ void FramebufferManager::SetDisplayFramebuffer(u32 framebuf, u32 stride, int for
}
}
std::vector<FramebufferInfo> FramebufferManager::GetFramebufferList()
{
std::vector<FramebufferInfo> FramebufferManager::GetFramebufferList() {
std::vector<FramebufferInfo> list;
for (auto iter = vfbs_.begin(); iter != vfbs_.end(); ++iter) {
@ -501,8 +536,11 @@ void FramebufferManager::DecimateFBOs() {
}
if ((*iter)->last_frame_used + FBO_OLD_AGE < gpuStats.numFrames) {
INFO_LOG(HLE, "Destroying FBO for %08x (%i x %i x %i)", vfb->fb_address, vfb->width, vfb->height, vfb->format)
textureCache_->NotifyFramebufferDestroyed(vfb->fb_address, vfb);
fbo_destroy(vfb->fbo);
if (vfb->fbo) {
textureCache_->NotifyFramebufferDestroyed(vfb->fb_address, vfb);
fbo_destroy(vfb->fbo);
vfb->fbo = 0;
}
delete vfb;
vfbs_.erase(iter++);
}
@ -515,7 +553,8 @@ void FramebufferManager::DestroyAllFBOs() {
for (auto iter = vfbs_.begin(); iter != vfbs_.end(); ++iter) {
VirtualFramebuffer *vfb = *iter;
textureCache_->NotifyFramebufferDestroyed(vfb->fb_address, vfb);
fbo_destroy(vfb->fbo);
if (vfb->fbo)
fbo_destroy(vfb->fbo);
delete vfb;
}
vfbs_.clear();

View File

@ -39,8 +39,9 @@ enum PspDisplayPixelFormat {
};
enum {
FB_USAGE_RENDERTARGET = 1,
FB_USAGE_TEXTURE = 2,
FB_USAGE_DISPLAYED_FRAMEBUFFER = 1,
FB_USAGE_RENDERTARGET = 2,
FB_USAGE_TEXTURE = 4,
};
@ -63,6 +64,8 @@ struct VirtualFramebuffer {
int format; // virtual, right now they are all RGBA8888
FBOColorDepth colorDepth;
FBO *fbo;
bool dirtyAfterDisplay;
};
@ -82,6 +85,7 @@ public:
void DecimateFBOs();
void BeginFrame();
void EndFrame();
void Resized();
void CopyDisplayToOutput();
void SetRenderFrameBuffer(); // Uses parameters computed from gstate

View File

@ -376,8 +376,7 @@ void ShaderManager::DirtyShader()
}
LinkedShader *ShaderManager::ApplyShader(int prim)
{
LinkedShader *ShaderManager::ApplyShader(int prim) {
if (globalDirty) {
if (lastShader)
lastShader->dirtyUniforms |= globalDirty;

View File

@ -138,7 +138,7 @@ void TextureCache::NotifyFramebuffer(u32 address, VirtualFramebuffer *framebuffe
if (entry) {
DEBUG_LOG(HLE, "Render to texture detected at %08x!", address);
if (!entry->framebuffer)
entry->framebuffer= framebuffer;
entry->framebuffer = framebuffer;
// TODO: Delete the original non-fbo texture too.
}
}
@ -695,12 +695,22 @@ void TextureCache::SetTexture() {
TexCache::iterator iter = cache.find(cachekey);
TexCacheEntry *entry = NULL;
gstate_c.flipTexture = false;
gstate_c.skipDrawReason &= ~SKIPDRAW_BAD_FB_TEXTURE;
if (iter != cache.end()) {
entry = &iter->second;
// Check for FBO - slow!
if (entry->framebuffer) {
fbo_bind_color_as_texture(entry->framebuffer->fbo, 0);
entry->framebuffer->usageFlags |= FB_USAGE_TEXTURE;
if (entry->framebuffer->fbo)
{
fbo_bind_color_as_texture(entry->framebuffer->fbo, 0);
}
else {
glBindTexture(GL_TEXTURE_2D, 0);
gstate_c.skipDrawReason |= SKIPDRAW_BAD_FB_TEXTURE;
}
UpdateSamplingParams(*entry, false);
// This isn't right.

View File

@ -23,6 +23,7 @@ public:
virtual u32 EnqueueList(u32 listpc, u32 stall, int subIntrBase, bool head);
virtual int listStatus(int listid);
virtual void DoState(PointerWrap &p);
virtual bool FramebufferDirty() { return true; }
protected:
typedef std::deque<DisplayList> DisplayListQueue;

View File

@ -102,6 +102,7 @@ public:
// Called by the window system if the window size changed. This will be reflected in PSPCoreParam.pixel*.
virtual void Resized() = 0;
virtual bool FramebufferDirty() = 0;
// Debugging
virtual void DumpNextFrame() = 0;

View File

@ -229,6 +229,8 @@ struct GPUgstate
enum SkipDrawReasonFlags {
SKIPDRAW_SKIPFRAME = 1,
SKIPDRAW_NON_DISPLAYED_FB = 2, // Skip drawing to FBO:s that have not been displayed.
SKIPDRAW_BAD_FB_TEXTURE = 4,
};
// The rest is cached simplified/converted data for fast access.

View File

@ -339,6 +339,7 @@
<None Include="..\android\jni\Application.mk" />
<None Include="..\CMakeLists.txt" />
<None Include="..\README.md" />
<None Include="git-version-gen.cmd" />
<None Include="ppsspp.ico" />
<None Include="icon1.ico" />
<None Include="rt_manif.bin" />

View File

@ -215,6 +215,7 @@
<None Include="..\CMakeLists.txt">
<Filter>Windows</Filter>
</None>
<None Include="git-version-gen.cmd" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="ppsspp.rc">

View File

@ -401,6 +401,8 @@ namespace MainWindow
case ID_OPTIONS_BUFFEREDRENDERING:
g_Config.bBufferedRendering = !g_Config.bBufferedRendering;
UpdateMenus();
if (gpu)
gpu->Resized(); // easy way to force a clear...
break;
case ID_OPTIONS_SHOWDEBUGSTATISTICS:
@ -416,7 +418,8 @@ namespace MainWindow
case ID_OPTIONS_STRETCHDISPLAY:
g_Config.bStretchToDisplay = !g_Config.bStretchToDisplay;
UpdateMenus();
gpu->Resized(); // easy way to force a clear...
if (gpu)
gpu->Resized(); // easy way to force a clear...
break;
case ID_OPTIONS_FRAMESKIP:
@ -443,12 +446,12 @@ namespace MainWindow
break;
case ID_DEBUG_DUMPNEXTFRAME:
gpu->DumpNextFrame();
if (gpu)
gpu->DumpNextFrame();
break;
case ID_DEBUG_LOADMAPFILE:
if (W32Util::BrowseForFileName(true, hWnd, "Load .MAP",0,"Maps\0*.map\0All files\0*.*\0\0","map",fn))
{
if (W32Util::BrowseForFileName(true, hWnd, "Load .MAP",0,"Maps\0*.map\0All files\0*.*\0\0","map",fn)) {
symbolMap.LoadSymbolMap(fn.c_str());
// HLE_PatchFunctions();
for (int i=0; i<numCPUs; i++)

View File

@ -314,10 +314,14 @@ void SettingsScreen::render() {
int y = 30;
int stride = 40;
UICheckBox(GEN_ID, x, y += stride, "Sound Emulation", ALIGN_TOPLEFT, &g_Config.bEnableSound);
UICheckBox(GEN_ID, x, y += stride, "Buffered Rendering", ALIGN_TOPLEFT, &g_Config.bBufferedRendering);
if (UICheckBox(GEN_ID, x, y += stride, "Buffered Rendering", ALIGN_TOPLEFT, &g_Config.bBufferedRendering)) {
gpu->Resized();
}
if (g_Config.bBufferedRendering) {
bool doubleRes = g_Config.iWindowZoom == 2;
UICheckBox(GEN_ID, x + 50, y += stride, "2x Render Resolution", ALIGN_TOPLEFT, &doubleRes);
if (UICheckBox(GEN_ID, x + 50, y += stride, "2x Render Resolution", ALIGN_TOPLEFT, &doubleRes)) {
gpu->Resized();
}
g_Config.iWindowZoom = doubleRes ? 2 : 1;
}
UICheckBox(GEN_ID, x, y += stride, "Hardware Transform", ALIGN_TOPLEFT, &g_Config.bHardwareTransform);
@ -444,8 +448,7 @@ void CreditsScreen::update(InputState &input_state) {
frames_++;
}
const static char *credits[] =
{
static const char * credits[] = {
"PPSSPP",
"",
"",