From bc156173928cdbd6296d29f855359e6475967e4d Mon Sep 17 00:00:00 2001 From: Henrik Rydgard Date: Sun, 3 Mar 2013 13:00:21 +0100 Subject: [PATCH] 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... --- Common/ArmEmitter.cpp | 4 +- Core/HLE/sceDisplay.cpp | 33 ++++++---- Core/MIPS/ARM/ArmJitCache.cpp | 2 +- GPU/GLES/DisplayListInterpreter.cpp | 23 +++++-- GPU/GLES/DisplayListInterpreter.h | 1 + GPU/GLES/FragmentShaderGenerator.cpp | 2 +- GPU/GLES/Framebuffer.cpp | 95 ++++++++++++++++++++-------- GPU/GLES/Framebuffer.h | 8 ++- GPU/GLES/ShaderManager.cpp | 3 +- GPU/GLES/TextureCache.cpp | 14 +++- GPU/GPUCommon.h | 1 + GPU/GPUInterface.h | 1 + GPU/GPUState.h | 2 + Windows/PPSSPP.vcxproj | 1 + Windows/PPSSPP.vcxproj.filters | 1 + Windows/WndMainWindow.cpp | 11 ++-- android/jni/MenuScreens.cpp | 11 ++-- 17 files changed, 149 insertions(+), 64 deletions(-) diff --git a/Common/ArmEmitter.cpp b/Common/ArmEmitter.cpp index 35b034d13e..9e4ba7abef 100644 --- a/Common/ArmEmitter.cpp +++ b/Common/ArmEmitter.cpp @@ -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); diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp index 17084d43c3..1e037fd5bc 100644 --- a/Core/HLE/sceDisplay.cpp +++ b/Core/HLE/sceDisplay.cpp @@ -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) diff --git a/Core/MIPS/ARM/ArmJitCache.cpp b/Core/MIPS/ARM/ArmJitCache.cpp index 62ec8fc496..57c86e9c45 100644 --- a/Core/MIPS/ARM/ArmJitCache.cpp +++ b/Core/MIPS/ARM/ArmJitCache.cpp @@ -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(); } diff --git a/GPU/GLES/DisplayListInterpreter.cpp b/GPU/GLES/DisplayListInterpreter.cpp index 8b4b568cf2..4e4582a15a 100644 --- a/GPU/GLES/DisplayListInterpreter.cpp +++ b/GPU/GLES/DisplayListInterpreter.cpp @@ -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; } diff --git a/GPU/GLES/DisplayListInterpreter.h b/GPU/GLES/DisplayListInterpreter.h index 048aab400b..9b30cab7b5 100644 --- a/GPU/GLES/DisplayListInterpreter.h +++ b/GPU/GLES/DisplayListInterpreter.h @@ -63,6 +63,7 @@ public: { return textureCache_.DecodeTexture(dest, state); } + virtual bool FramebufferDirty(); std::vector GetFramebufferList(); diff --git a/GPU/GLES/FragmentShaderGenerator.cpp b/GPU/GLES/FragmentShaderGenerator.cpp index 24777ca920..321ae0b0c0 100644 --- a/GPU/GLES/FragmentShaderGenerator.cpp +++ b/GPU/GLES/FragmentShaderGenerator.cpp @@ -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) diff --git a/GPU/GLES/Framebuffer.cpp b/GPU/GLES/Framebuffer.cpp index 8b4f9d5a09..d2f7e931dd 100644 --- a/GPU/GLES/Framebuffer.cpp +++ b/GPU/GLES/Framebuffer.cpp @@ -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 FramebufferManager::GetFramebufferList() -{ +std::vector FramebufferManager::GetFramebufferList() { std::vector 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(); diff --git a/GPU/GLES/Framebuffer.h b/GPU/GLES/Framebuffer.h index c40fcc7c24..842294373f 100644 --- a/GPU/GLES/Framebuffer.h +++ b/GPU/GLES/Framebuffer.h @@ -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 diff --git a/GPU/GLES/ShaderManager.cpp b/GPU/GLES/ShaderManager.cpp index ac789f5726..4675f960f8 100644 --- a/GPU/GLES/ShaderManager.cpp +++ b/GPU/GLES/ShaderManager.cpp @@ -376,8 +376,7 @@ void ShaderManager::DirtyShader() } -LinkedShader *ShaderManager::ApplyShader(int prim) -{ +LinkedShader *ShaderManager::ApplyShader(int prim) { if (globalDirty) { if (lastShader) lastShader->dirtyUniforms |= globalDirty; diff --git a/GPU/GLES/TextureCache.cpp b/GPU/GLES/TextureCache.cpp index cd1a4b6b52..8b4d202e2d 100644 --- a/GPU/GLES/TextureCache.cpp +++ b/GPU/GLES/TextureCache.cpp @@ -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. diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 8feb9cb569..88aa151a8d 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -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 DisplayListQueue; diff --git a/GPU/GPUInterface.h b/GPU/GPUInterface.h index 4edb5de1c5..f558e3ceb8 100644 --- a/GPU/GPUInterface.h +++ b/GPU/GPUInterface.h @@ -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; diff --git a/GPU/GPUState.h b/GPU/GPUState.h index 7ddb5853c0..853a05b4e8 100644 --- a/GPU/GPUState.h +++ b/GPU/GPUState.h @@ -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. diff --git a/Windows/PPSSPP.vcxproj b/Windows/PPSSPP.vcxproj index 019fde5bcc..04c498d664 100644 --- a/Windows/PPSSPP.vcxproj +++ b/Windows/PPSSPP.vcxproj @@ -339,6 +339,7 @@ + diff --git a/Windows/PPSSPP.vcxproj.filters b/Windows/PPSSPP.vcxproj.filters index 6c0a8b3408..7456a22f8a 100644 --- a/Windows/PPSSPP.vcxproj.filters +++ b/Windows/PPSSPP.vcxproj.filters @@ -215,6 +215,7 @@ Windows + diff --git a/Windows/WndMainWindow.cpp b/Windows/WndMainWindow.cpp index 998b21bdb2..b6b769f655 100644 --- a/Windows/WndMainWindow.cpp +++ b/Windows/WndMainWindow.cpp @@ -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; iResized(); + } 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", "", "",