From a6eb25b58c2f325c714949c7a6ad5351b7aef9bb Mon Sep 17 00:00:00 2001 From: Henrik Rydgard Date: Sun, 18 Nov 2012 02:48:15 +0100 Subject: [PATCH 01/12] Disable blend on clear mode --- GPU/GLES/DisplayListInterpreter.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/GPU/GLES/DisplayListInterpreter.cpp b/GPU/GLES/DisplayListInterpreter.cpp index e70bd6b6fb..d927f94326 100644 --- a/GPU/GLES/DisplayListInterpreter.cpp +++ b/GPU/GLES/DisplayListInterpreter.cpp @@ -172,6 +172,7 @@ void EnterClearMode(u32 data) glColorMask(colMask, colMask, colMask, alphaMask); glDepthMask(updateZ); // Update Z or not // Note that depth test must be enabled for depth writes to go through! So we use GL_ALWAYS + glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_ALWAYS); glDisable(GL_CULL_FACE); // ?? @@ -189,7 +190,7 @@ void LeaveClearMode() glColorMask(1,1,1,1); glEnDis(GL_DEPTH_TEST, gstate.zTestEnable & 1); glDepthFunc(GL_LEQUAL); // TODO - + glEnDis(GL_BLEND, gstate.alphaBlendEnable & 1); // dirtyshader? } From e37a1fb1d35f72e88323d4e5035c6c527425574b Mon Sep 17 00:00:00 2001 From: Henrik Rydgard Date: Sun, 18 Nov 2012 13:04:49 +0100 Subject: [PATCH 02/12] Add internal 2D drawing library that goes through the Ge emulation for portability. To be used for things like sceUtility* and other overlays like FPS and stats. --- .gitignore | 1 + Common/Common.vcxproj | 3 + Common/Common.vcxproj.filters | 3 + Common/Thread.cpp | 2 +- Core/CMakeLists.txt | 2 + Core/Core.vcxproj | 4 + Core/Core.vcxproj.filters | 12 + Core/HLE/sceDisplay.cpp | 11 + Core/HLE/sceGe.cpp | 40 +++- Core/HLE/sceGe.h | 7 + Core/HLE/sceImpose.cpp | 6 +- Core/HLE/sceKernel.cpp | 7 + Core/HLE/sceUtility.cpp | 21 +- Core/Util/PPGeDraw.cpp | 331 ++++++++++++++++++++++++++++ Core/Util/PPGeDraw.h | 65 ++++++ Core/Util/ppge_atlas.cpp | 125 +++++++++++ Core/Util/ppge_atlas.h | 20 ++ GPU/GLES/DisplayListInterpreter.cpp | 85 ++++--- GPU/GLES/DisplayListInterpreter.h | 5 + GPU/GLES/Framebuffer.cpp | 3 +- GPU/GLES/ShaderManager.cpp | 3 + GPU/GLES/ShaderManager.h | 3 +- GPU/GLES/TextureCache.cpp | 4 +- GPU/GLES/TransformPipeline.cpp | 24 +- GPU/GLES/VertexDecoder.cpp | 20 +- GPU/GPUInterface.h | 3 + GPU/GPUState.cpp | 31 ++- GPU/GPUState.h | 37 +++- GPU/Null/NullGpu.cpp | 77 ++++--- GPU/Null/NullGpu.h | 5 + SDL/buildassets.sh | 1 + Windows/PPSSPP.vcxproj | 5 +- android/assets/ppge_atlas.zim | Bin 0 -> 131088 bytes android/buildassets.sh | 1 + android/jni/Android.mk | 5 +- build_ppgeatlas.sh | 1 + main.cpp | 2 +- ppge_atlas.zim | Bin 0 -> 131088 bytes ppge_atlasscript.txt | 8 + 39 files changed, 847 insertions(+), 136 deletions(-) create mode 100644 Core/Util/PPGeDraw.cpp create mode 100644 Core/Util/PPGeDraw.h create mode 100644 Core/Util/ppge_atlas.cpp create mode 100644 Core/Util/ppge_atlas.h create mode 100644 android/assets/ppge_atlas.zim create mode 100644 build_ppgeatlas.sh create mode 100644 ppge_atlas.zim create mode 100644 ppge_atlasscript.txt diff --git a/.gitignore b/.gitignore index 71e7b81344..f2e96b314a 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ Memstick android/ui_atlas.zim __testoutput.txt __testerror.txt +ppge_atlas.zim.png diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj index 85d3a82b1e..e7db622210 100644 --- a/Common/Common.vcxproj +++ b/Common/Common.vcxproj @@ -245,6 +245,9 @@ + + + diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters index 14140af678..c55369f88c 100644 --- a/Common/Common.vcxproj.filters +++ b/Common/Common.vcxproj.filters @@ -77,4 +77,7 @@ + + + \ No newline at end of file diff --git a/Common/Thread.cpp b/Common/Thread.cpp index f3dc162574..77815bfb27 100644 --- a/Common/Thread.cpp +++ b/Common/Thread.cpp @@ -129,7 +129,7 @@ void SetThreadAffinity(std::thread::native_handle_type thread, u32 mask) #ifdef __APPLE__ thread_policy_set(pthread_mach_thread_np(thread), THREAD_AFFINITY_POLICY, (integer_t *)&mask, 1); -#elif defined __linux__ || defined BSD4_4 +#elif (defined __linux__ || defined BSD4_4) && !defined(ANDROID) cpu_set_t cpu_set; CPU_ZERO(&cpu_set); diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index bbf5e38704..a8aac2e573 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -54,6 +54,8 @@ set(SRCS FileSystems/DirectoryFileSystem.cpp FileSystems/MetaFileSystem.cpp Util/BlockAllocator.cpp + Util/ppge_atlas.cpp + Util/PPGeDraw.cpp CPU.cpp CoreTiming.cpp Config.cpp diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 426a12c298..a11d944332 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -236,6 +236,8 @@ + + @@ -337,6 +339,8 @@ + + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index a29237c2a8..0d1ff26051 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -291,6 +291,12 @@ HLE\Libraries + + Util + + + Util + @@ -530,6 +536,12 @@ HLE\Libraries + + Util + + + Util + diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp index f5af289bd3..35ccc4d707 100644 --- a/Core/HLE/sceDisplay.cpp +++ b/Core/HLE/sceDisplay.cpp @@ -47,6 +47,9 @@ #include "../../GPU/GLES/ShaderManager.h" #include "../../GPU/GPUState.h" +// Internal drawing library +#include "../Util/PPGeDraw.h" + extern ShaderManager shaderManager; struct FrameBufferState @@ -138,6 +141,14 @@ void hleEnterVblank(u64 userdata, int cyclesLate) framebufIsLatched = false; } + // Draw screen overlays before blitting. Saves and restores the Ge context. + + /* + PPGeBegin(); + PPGeDrawImage(I_LOGO, 5, 5, 0, 0xFFFFFFFF); + PPGeDrawText("This is PPGeDraw speaking", 10, 100, 0, 0.5f, 0xFFFFFFFF); + PPGeEnd(); + */ // Yeah, this has to be the right moment to end the frame. Should possibly blit the right buffer // depending on what's set in sceDisplaySetFramebuf, in order to support half-framerate games - // an initial hack could be to NOT end the frame if the buffer didn't change? that should work okay. diff --git a/Core/HLE/sceGe.cpp b/Core/HLE/sceGe.cpp index 9e4e475a73..082a6dd4a5 100644 --- a/Core/HLE/sceGe.cpp +++ b/Core/HLE/sceGe.cpp @@ -154,14 +154,42 @@ void sceGeUnsetCallback(u32 cbID) sceKernelReleaseSubIntrHandler(PSP_GE_INTR, PSP_GE_SUBINTR_SIGNAL); } -void sceGeSaveContext() +// Points to 512 32-bit words, where we can probably layout the context however we want +// unless some insane game pokes it and relies on it... +u32 sceGeSaveContext(u32 ctxAddr) { - ERROR_LOG(HLE,"UNIMPL sceGeSaveContext()"); + DEBUG_LOG(HLE,"sceGeSaveContext(%08x)", ctxAddr); + + if (sizeof(gstate) > 512 * 4) { + ERROR_LOG(HLE, "AARGH! sizeof(gstate) has grown too large!"); + return 0; + } + + // Let's just dump gstate. + if (Memory::IsValidAddress(ctxAddr)) { + Memory::WriteStruct(ctxAddr, &gstate); + } + + // This action should probably be pushed to the end of the queue of the display thread - + // when we have one. + return 0; } -void sceGeRestoreContext() +u32 sceGeRestoreContext(u32 ctxAddr) { - ERROR_LOG(HLE,"UNIMPL sceGeRestoreContext()"); + DEBUG_LOG(HLE,"sceGeRestoreContext(%08x)", ctxAddr); + + if (sizeof(gstate) > 512 * 4) { + ERROR_LOG(HLE, "AARGH! sizeof(gstate) has grown too large!"); + return 0; + } + + if (Memory::IsValidAddress(ctxAddr)) { + Memory::ReadStruct(ctxAddr, &gstate); + } + ReapplyGfxState(); + + return 0; } void sceGeGetMtx() @@ -195,8 +223,8 @@ const HLEFunction sceGe_user[] = {0xB77905EA,&sceGeEdramSetAddrTranslation,"sceGeEdramSetAddrTranslation"}, {0xDC93CFEF,0,"sceGeGetCmd"}, {0x57C8945B,&sceGeGetMtx,"sceGeGetMtx"}, - {0x438A385A,0,"sceGeSaveContext"}, - {0x0BF608FB,0,"sceGeRestoreContext"}, + {0x438A385A,&WrapU_U,"sceGeSaveContext"}, + {0x0BF608FB,&WrapU_U,"sceGeRestoreContext"}, {0x5FB86AB0,0,"sceGeListDeQueue"}, }; diff --git a/Core/HLE/sceGe.h b/Core/HLE/sceGe.h index 9a418743c7..7b92c863b8 100644 --- a/Core/HLE/sceGe.h +++ b/Core/HLE/sceGe.h @@ -38,3 +38,10 @@ void Register_sceGe_user(); void __GeInit(); void __GeShutdown(); + + +// Export functions for use by Util/PPGe +u32 sceGeRestoreContext(u32 ctxAddr); +u32 sceGeSaveContext(u32 ctxAddr); + +u32 sceGeListEnQueue(u32 listAddress, u32 stallAddress, u32 callbackId, u32 optParamAddr); diff --git a/Core/HLE/sceImpose.cpp b/Core/HLE/sceImpose.cpp index d37ac1f171..93a417a088 100644 --- a/Core/HLE/sceImpose.cpp +++ b/Core/HLE/sceImpose.cpp @@ -39,7 +39,7 @@ static u32 iButtonValue = 0; u32 sceImposeGetBatteryIconStatus(u32 chargingPtr, u32 iconStatusPtr) { - DEBUG_LOG(HLE,"%i=sceImposeGetBatteryIconStatus(%08x, %08x)", chargingPtr, iconStatusPtr); + DEBUG_LOG(HLE, "sceImposeGetBatteryIconStatus(%08x, %08x)", chargingPtr, iconStatusPtr); if (Memory::IsValidAddress(chargingPtr)) Memory::Write_U32(1, chargingPtr); if (Memory::IsValidAddress(iconStatusPtr)) @@ -49,7 +49,7 @@ u32 sceImposeGetBatteryIconStatus(u32 chargingPtr, u32 iconStatusPtr) u32 sceImposeSetLanguageMode(u32 languageVal, u32 buttonVal) { - DEBUG_LOG(HLE,"%i=sceImposeSetLanguageMode(%08x, %08x)", languageVal, buttonVal); + DEBUG_LOG(HLE, "sceImposeSetLanguageMode(%08x, %08x)", languageVal, buttonVal); iLanguage = languageVal; iButtonValue = buttonVal; return 0; @@ -57,7 +57,7 @@ u32 sceImposeSetLanguageMode(u32 languageVal, u32 buttonVal) u32 sceImposeGetLanguageMode(u32 languagePtr, u32 btnPtr) { - DEBUG_LOG(HLE,"%i=sceImposeGetLanguageMode(%08x, %08x)", languagePtr, btnPtr); + DEBUG_LOG(HLE, "sceImposeGetLanguageMode(%08x, %08x)", languagePtr, btnPtr); if (Memory::IsValidAddress(languagePtr)) Memory::Write_U32(iLanguage, languagePtr); if (Memory::IsValidAddress(btnPtr)) diff --git a/Core/HLE/sceKernel.cpp b/Core/HLE/sceKernel.cpp index c466ce63ad..37f54cec7d 100644 --- a/Core/HLE/sceKernel.cpp +++ b/Core/HLE/sceKernel.cpp @@ -49,6 +49,8 @@ #include "sceUtility.h" #include "sceUmd.h" +#include "../Util/PPGeDraw.h" + extern MetaFileSystem pspFileSystem; /* @@ -78,6 +80,9 @@ void __KernelInit() __PowerInit(); __UtilityInit(); __UmdInit(); + + // "Internal" PSP libraries + __PPGeInit(); kernelRunning = true; INFO_LOG(HLE, "Kernel initialized."); @@ -94,6 +99,8 @@ void __KernelShutdown() INFO_LOG(HLE, "Shutting down kernel - %i kernel objects alive", kernelObjects.GetCount()); kernelObjects.Clear(); + __PPGeShutdown(); + __GeShutdown(); __AudioShutdown(); __IoShutdown(); diff --git a/Core/HLE/sceUtility.cpp b/Core/HLE/sceUtility.cpp index 7dec4ae316..d3b50a28a0 100644 --- a/Core/HLE/sceUtility.cpp +++ b/Core/HLE/sceUtility.cpp @@ -22,6 +22,8 @@ #include "sceKernelThread.h" #include "sceUtility.h" +#include "sceCtrl.h" +#include "../Util/PPGeDraw.h" enum SceUtilitySavedataType { @@ -288,17 +290,20 @@ struct pspMessageDialog u32 buttonPressed; // 0=?, 1=Yes, 2=No, 3=Back }; +static pspMessageDialog messageDialog; + void sceUtilityMsgDialogInitStart() { - DEBUG_LOG(HLE,"FAKE sceUtilityMsgDialogInitStart(%i)", PARAM(0)); - pspMessageDialog *dlg = (pspMessageDialog *)Memory::GetPointer(PARAM(0)); - if (dlg->type == 0) // number + u32 structAddr = PARAM(0); + DEBUG_LOG(HLE,"FAKE sceUtilityMsgDialogInitStart(%i)", structAddr); + Memory::ReadStruct(structAddr, &messageDialog); + if (messageDialog.type == 0) // number { - INFO_LOG(HLE, "MsgDialog: %08x", dlg->errorNum); + INFO_LOG(HLE, "MsgDialog: %08x", messageDialog.errorNum); } else { - INFO_LOG(HLE, "MsgDialog: %s", dlg->string); + INFO_LOG(HLE, "MsgDialog: %s", messageDialog.string); } __UtilityInitStart(); } @@ -314,6 +319,9 @@ void sceUtilityMsgDialogUpdate() { DEBUG_LOG(HLE,"FAKE sceUtilityMsgDialogUpdate(%i)", PARAM(0)); __UtilityUpdate(); + // PPGeBegin(); + + // PPGeEnd(); RETURN(0); } @@ -323,6 +331,9 @@ void sceUtilityMsgDialogGetStatus() RETURN(__UtilityGetStatus()); } + +// On screen keyboard + void sceUtilityOskInitStart() { DEBUG_LOG(HLE,"FAKE sceUtilityOskInitStart(%i)", PARAM(0)); diff --git a/Core/Util/PPGeDraw.cpp b/Core/Util/PPGeDraw.cpp new file mode 100644 index 0000000000..a125470646 --- /dev/null +++ b/Core/Util/PPGeDraw.cpp @@ -0,0 +1,331 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "PPGeDraw.h" +#include "../GPU/ge_constants.h" +#include "../GPU/GPUState.h" +#include "../GPU/GPUInterface.h" +#include "../HLE/sceKernel.h" +#include "../HLE/sceKernelMemory.h" +#include "../HLE/sceGe.h" +#include "../MemMap.h" +#include "image/zim_load.h" +#include "gfx/texture_atlas.h" +#include "../System.h" + +static u32 atlasPtr; + +struct PPGeVertex { + u16 u, v; + u32 color; + s16 x, y; u16 z; +}; + +u32 savedContextPtr; +u32 savedContextSize = 512 * 4; + +// Display list writer +u32 dlPtr; +u32 dlWritePtr; +u32 dlSize = 0x10000; // should be enough for a frame of gui... + +u32 dataPtr; +u32 dataWritePtr; +u32 dataSize = 0x10000; // should be enough for a frame of gui... + +// Vertex collector +u32 vertexStart; +u32 vertexCount; + +//only 0xFFFFFF of data is used +static void WriteCmd(u8 cmd, u32 data) { + Memory::Write_U32((cmd << 24) | (data & 0xFFFFFF), dlWritePtr); + dlWritePtr += 4; +} + +static void WriteCmdAddrWithBase(u8 cmd, u32 addr) { + WriteCmd(GE_CMD_BASE, (addr >> 8) & 0xFF0000); + WriteCmd(cmd, addr & 0xFFFFFF); +} + +/* +static void WriteCmdFloat(u8 cmd, float f) { + union { + float fl; + u32 u; + } conv; + conv.fl = f; + WriteCmd(cmd, conv.u >> 8); +}*/ + +static void BeginVertexData() { + vertexCount = 0; + vertexStart = dataWritePtr; +} + +static void Vertex(float x, float y, float u, float v, u32 color = 0xFFFFFFFF) +{ + PPGeVertex vtx; + vtx.x = x - 0.5f; vtx.y = y - 0.5f; vtx.z = 0; + vtx.u = u * 256 - 0.5f; vtx.v = v * 256 - 0.5f; + vtx.color = color; + Memory::WriteStruct(dataWritePtr, &vtx); + vertexCount++; + dataWritePtr += sizeof(vtx); +} + +static void EndVertexDataAndDraw(int prim) { + WriteCmdAddrWithBase(GE_CMD_VADDR, vertexStart); + WriteCmd(GE_CMD_PRIM, (prim << 16) | vertexCount); +} + +void __PPGeInit() +{ + if (PSP_CoreParameter().gpuCore == GPU_NULL) { + // Let's just not bother. + dlPtr = 0; + NOTICE_LOG(HLE, "Not initializing PPGe - GPU is NullGpu"); + return; + } + u8 *imageData; + int width; + int height; + int flags; + if (!LoadZIM("ppge_atlas.zim", &width, &height, &flags, &imageData)) { + ERROR_LOG(HLE, "PPGe init failed - no atlas texture. PPGe stuff will not be drawn."); + return; + } + + u32 atlasSize = height * width * 2; // it's a 4444 texture + dlPtr = kernelMemory.Alloc(dlSize, false, "PPGe Display List"); + dataPtr = kernelMemory.Alloc(dataSize, false, "PPGe Vertex Data"); + atlasPtr = kernelMemory.Alloc(atlasSize, false, "PPGe Atlas Texture"); + savedContextPtr = kernelMemory.Alloc(savedContextSize, false, "PPGe Saved Context"); + + u16 *imagePtr = (u16 *)imageData; + // component order change + for (int i = 0; i < width * height; i++) { + u16 c = imagePtr[i]; + int a = c & 0xF; + int r = (c >> 4) & 0xF; + int g = (c >> 8) & 0xF; + int b = (c >> 12) & 0xF; + c = (a << 12) | (r << 8) | (g << 4) | b; + imagePtr[i] = c; + } + + Memory::Memcpy(atlasPtr, imageData, atlasSize); + free(imageData); + + NOTICE_LOG(HLE, "PPGe drawing library initialized. DL: %08x Data: %08x Atlas: %08x (%i) Ctx: %08x", + dlPtr, dataPtr, atlasPtr, atlasSize, savedContextPtr); +} + +void __PPGeShutdown() +{ + kernelMemory.Free(atlasPtr); + kernelMemory.Free(dataPtr); + kernelMemory.Free(dlPtr); + kernelMemory.Free(savedContextPtr); + atlasPtr = 0; + dataPtr = 0; + dlPtr = 0; + savedContextPtr = 0; +} + +void PPGeBegin() +{ + if (!dlPtr) + return; + + // Reset write pointers to start of command and data buffers. + dlWritePtr = dlPtr; + dataWritePtr = dataPtr; + + // Set up the correct states for UI drawing + WriteCmd(GE_CMD_ALPHABLENDENABLE, 1); + WriteCmd(GE_CMD_BLENDMODE, 2 | (3 << 4)); + WriteCmd(GE_CMD_ALPHATESTENABLE, 0); + WriteCmd(GE_CMD_COLORTESTENABLE, 0); + WriteCmd(GE_CMD_ZTESTENABLE, 0); + WriteCmd(GE_CMD_FOGENABLE, 0); + WriteCmd(GE_CMD_STENCILTESTENABLE, 0); + WriteCmd(GE_CMD_CULLFACEENABLE, 0); + WriteCmd(GE_CMD_CLEARMODE, 0); // Normal mode + + WriteCmd(GE_CMD_TEXTUREMAPENABLE, 1); + WriteCmd(GE_CMD_TEXSIZE0, 8 | (8 << 8)); // 1 << (7+1) = 256 + WriteCmd(GE_CMD_TEXMAPMODE, 0 | (1 << 8)); + WriteCmd(GE_CMD_TEXMODE, 0); + WriteCmd(GE_CMD_TEXFORMAT, 2); // 4444 + WriteCmd(GE_CMD_TEXFILTER, (1 << 8) | 1); // mag = LINEAR min = LINEAR + WriteCmd(GE_CMD_TEXWRAP, (1 << 8) | 1); // clamp texture wrapping + WriteCmd(GE_CMD_TEXFUNC, (0 << 16) | (1 << 8) | 0); // RGBA texture reads, modulate, no color doubling + WriteCmd(GE_CMD_TEXADDR0, atlasPtr & 0xFFFFF0); + WriteCmd(GE_CMD_TEXBUFWIDTH0, 256 | ((atlasPtr & 0xFF000000) >> 8)); + WriteCmd(GE_CMD_TEXFLUSH, 0); + + WriteCmd(GE_CMD_SCISSOR1, (0 << 10) | 0); + WriteCmd(GE_CMD_SCISSOR2, (1023 << 10) | 1023); + WriteCmd(GE_CMD_MINZ, 0); + WriteCmd(GE_CMD_MAXZ, 0xFFFF); + + // Through mode, so we don't have to bother with matrices + WriteCmd(GE_CMD_VERTEXTYPE, GE_VTYPE_TC_16BIT | GE_VTYPE_COL_8888 | GE_VTYPE_POS_16BIT | GE_VTYPE_THROUGH); +} + +void PPGeEnd() +{ + if (!dlPtr) + return; + + WriteCmd(GE_CMD_FINISH, 0); + WriteCmd(GE_CMD_END, 0); + + if (dataWritePtr > dataPtr) { + sceGeSaveContext(savedContextPtr); + gpu->EnableInterrupts(false); + + // We actually drew something + u32 list = sceGeListEnQueue(dlPtr, dlWritePtr, 0, 0); + DEBUG_LOG(HLE, "PPGe enqueued display list %i", list); + // TODO: Might need to call some internal trickery function when this is actually synchronous. + // sceGeListSync(u32 displayListID, 1); //0 : wait for completion 1:check and return + gpu->EnableInterrupts(true); + sceGeRestoreContext(savedContextPtr); + } + +} + +static void PPGeMeasureText(const char *text, float scale, float *w, float *h) { + const AtlasFont &atlasfont = *ppge_atlas.fonts[0]; + unsigned char cval; + float wacc = 0; + int lines = 1; + while ((cval = *text++) != '\0') { + if (cval < 32) continue; + if (cval > 127) continue; + if (cval == '\n') { + wacc = 0; + lines++; + } + AtlasChar c = atlasfont.chars[cval - 32]; + wacc += c.wx * scale; + } + if (w) *w = wacc; + if (h) *h = atlasfont.height * scale * lines; +} + +static void PPGeDoAlign(int flags, float *x, float *y, float *w, float *h) { + if (flags & PPGE_ALIGN_HCENTER) *x -= *w / 2; + if (flags & PPGE_ALIGN_RIGHT) *x -= *w; + if (flags & PPGE_ALIGN_VCENTER) *y -= *h / 2; + if (flags & PPGE_ALIGN_BOTTOM) *y -= *h; +} + +// Draws some text using the one font we have. +// Mostly stolen from DrawBuffer. +void PPGeDrawText(const char *text, float x, float y, int align, float scale, u32 color) +{ + if (!dlPtr) + return; + const AtlasFont &atlasfont = *ppge_atlas.fonts[0]; + unsigned char cval; + float w, h; + PPGeMeasureText(text, scale, &w, &h); + if (align) { + PPGeDoAlign(align, &x, &y, &w, &h); + } + BeginVertexData(); + y += atlasfont.ascend*scale; + float sx = x; + while ((cval = *text++) != '\0') { + if (cval == '\n') { + y += atlasfont.height * scale; + x = sx; + continue; + } + if (cval < 32) continue; + if (cval > 127) continue; + AtlasChar c = atlasfont.chars[cval - 32]; + float cx1 = x + c.ox * scale; + float cy1 = y + c.oy * scale; + float cx2 = x + (c.ox + c.pw) * scale; + float cy2 = y + (c.oy + c.ph) * scale; + Vertex(cx1, cy1, c.sx, c.sy, color); + Vertex(cx2, cy2, c.ex, c.ey, color); + x += c.wx * scale; + } + EndVertexDataAndDraw(GE_PRIM_RECTANGLES); +} + +// Draws a "4-patch" for button-like things that can be resized +void PPGeDraw4Patch(int atlasImage, float x, float y, float w, float h, u32 color) +{ + if (!dlPtr) + return; + const AtlasImage &img = ppge_images[atlasImage]; + float borderx = img.w / 2; + float bordery = img.h / 2; + float u1 = img.u1, uhalf = (img.u1 + img.u2) / 2, u2 = img.u2; + float v1 = img.v1, vhalf = (img.v1 + img.v2) / 2, v2 = img.v2; + float xmid1 = x + borderx; + float xmid2 = x + w - borderx; + float ymid1 = y + bordery; + float ymid2 = y + h - bordery; + float x2 = x + w; + float y2 = y + h; + BeginVertexData(); + // Top row + Vertex(x, y, u1, v1, color); + Vertex(xmid1, ymid1, uhalf, vhalf, color); + Vertex(xmid1, y, uhalf, v1, color); + Vertex(xmid2, ymid1, uhalf, vhalf, color); + Vertex(xmid2, y, uhalf, v1, color); + Vertex(x2, ymid1, u2, vhalf, color); + // Middle row + Vertex(x, ymid1, u1, vhalf, color); + Vertex(xmid1, ymid2, uhalf, vhalf, color); + Vertex(xmid1, ymid1, uhalf, vhalf, color); + Vertex(xmid2, ymid2, uhalf, vhalf, color); + Vertex(xmid2, ymid1, uhalf, vhalf, color); + Vertex(x2, ymid2, u2, v2, color); + // Bottom row + Vertex(x, ymid2, u1, vhalf, color); + Vertex(xmid1, y2, uhalf, v2, color); + Vertex(xmid1, ymid2, uhalf, vhalf, color); + Vertex(xmid2, y2, uhalf, v2, color); + Vertex(xmid2, ymid2, uhalf, vhalf, color); + Vertex(x2, y2, u2, v2, color); + EndVertexDataAndDraw(GE_PRIM_RECTANGLES); +} + +// Just blits an image to the screen, multiplied with the color. +void PPGeDrawImage(int atlasImage, float x, float y, int align, u32 color) +{ + if (!dlPtr) + return; + + const AtlasImage &img = ppge_atlas.images[atlasImage]; + float w = img.w; + float h = img.h; + BeginVertexData(); + Vertex(x, y, img.u1, img.v1, color); + Vertex(x + w, y + h, img.u2, img.v2, color); + EndVertexDataAndDraw(GE_PRIM_RECTANGLES); +} + diff --git a/Core/Util/PPGeDraw.h b/Core/Util/PPGeDraw.h new file mode 100644 index 0000000000..70018ba867 --- /dev/null +++ b/Core/Util/PPGeDraw.h @@ -0,0 +1,65 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include "../../Globals.h" +#include "ppge_atlas.h" + +///////////////////////////////////////////////////////////////////////////////////////////// +// PPGeDraw: Super simple internal drawing API for 2D overlays like sceUtility messageboxes +// etc. Goes through the Ge emulation so that it's 100% portable - will work +// splendidly on any existing GPU backend, including the future software backend. + +// Uploads the necessary texture atlas and other data to kernel RAM, and reserves +// space for the display list. The PSP must be inited. +void __PPGeInit(); + +// Just frees up the allocated kernel memory. +void __PPGeShutdown(); + +// Save and restore the Ge context. PPGeEnd() kicks off the generated display list. +void PPGeBegin(); +void PPGeEnd(); + +enum { + PPGE_ALIGN_LEFT = 0, + PPGE_ALIGN_RIGHT = 16, + PPGE_ALIGN_TOP = 0, + PPGE_ALIGN_BOTTOM = 1, + PPGE_ALIGN_HCENTER = 4, + PPGE_ALIGN_VCENTER = 8, + PPGE_ALIGN_VBASELINE = 32, // text only, possibly not yet working + + PPGE_ALIGN_CENTER = PPGE_ALIGN_HCENTER | PPGE_ALIGN_VCENTER, + PPGE_ALIGN_TOPLEFT = PPGE_ALIGN_TOP | PPGE_ALIGN_LEFT, + PPGE_ALIGN_TOPRIGHT = PPGE_ALIGN_TOP | PPGE_ALIGN_RIGHT, + PPGE_ALIGN_BOTTOMLEFT = PPGE_ALIGN_BOTTOM | PPGE_ALIGN_LEFT, + PPGE_ALIGN_BOTTOMRIGHT = PPGE_ALIGN_BOTTOM | PPGE_ALIGN_RIGHT, +}; + +// These functions must be called between PPGeBegin and PPGeEnd. + +// Draws some text using the one font we have. +void PPGeDrawText(const char *text, float x, float y, int align, float scale, u32 color); + +// Draws a "4-patch" for button-like things that can be resized +void PPGeDraw4Patch(int atlasImage, float x, float y, float w, float h, u32 color); + +// Just blits an image to the screen, multiplied with the color. +void PPGeDrawImage(int atlasImage, float x, float y, int align, u32 color); + diff --git a/Core/Util/ppge_atlas.cpp b/Core/Util/ppge_atlas.cpp new file mode 100644 index 0000000000..7a8bf0fde9 --- /dev/null +++ b/Core/Util/ppge_atlas.cpp @@ -0,0 +1,125 @@ +// C++ generated by atlastool from ppge_atlasscript.txt (hrydgard@gmail.com) + +#include "ppge_atlas.h" + +const AtlasFont font_UBUNTU24 = { + -1.375000f, // padding + 36.687500f, // height + 26.937500f, // ascend + 0.750000f, // distslope + { + {0.972656f, 0.000000f, 0.976563f, 0.003906f, -2.0000f, -2.0000f, 7.0625f, 1, 1}, // 32 + {0.531250f, 0.675781f, 0.562500f, 0.777344f, -0.3750f, -23.1250f, 6.3750f, 8, 26}, // 33 + {0.246094f, 0.718750f, 0.292969f, 0.765625f, -0.5000f, -23.0625f, 10.2500f, 12, 12}, // 34 + {0.000000f, 0.191406f, 0.074219f, 0.292969f, -1.5625f, -23.0625f, 15.3125f, 19, 26}, // 35 + {0.527344f, 0.464844f, 0.589844f, 0.566406f, -1.1875f, -23.0625f, 13.8125f, 16, 26}, // 36 + {0.867188f, 0.105469f, 0.957031f, 0.207031f, -0.7500f, -23.3750f, 20.9375f, 23, 26}, // 37 + {0.000000f, 0.296875f, 0.066406f, 0.398438f, -0.9375f, -23.0625f, 14.1250f, 17, 26}, // 38 + {0.656250f, 0.140625f, 0.683594f, 0.187500f, -0.5000f, -23.0625f, 5.3125f, 7, 12}, // 39 + {0.425781f, 0.449219f, 0.472656f, 0.589844f, 0.3750f, -26.9375f, 10.7500f, 12, 36}, // 40 + {0.703125f, 0.453125f, 0.750000f, 0.593750f, -1.3750f, -26.9375f, 10.7500f, 12, 36}, // 41 + {0.933594f, 0.691406f, 0.996094f, 0.750000f, -1.2500f, -23.0625f, 12.8750f, 16, 15}, // 42 + {0.867188f, 0.691406f, 0.929688f, 0.753906f, -0.5000f, -18.3750f, 14.7500f, 16, 16}, // 43 + {0.593750f, 0.464844f, 0.625000f, 0.511719f, -0.5000f, -4.9375f, 5.7500f, 8, 12}, // 44 + {0.148438f, 0.191406f, 0.187500f, 0.214844f, -1.4375f, -12.6250f, 7.1250f, 10, 6}, // 45 + {0.421875f, 0.316406f, 0.453125f, 0.343750f, -0.5000f, -4.9375f, 6.1250f, 8, 7}, // 46 + {0.363281f, 0.421875f, 0.421875f, 0.535156f, -3.0625f, -23.0625f, 9.1250f, 15, 29}, // 47 + {0.070313f, 0.304688f, 0.136719f, 0.406250f, -0.7500f, -23.0625f, 15.4375f, 17, 26}, // 48 + {0.960938f, 0.105469f, 0.996094f, 0.207031f, -1.7500f, -23.0625f, 6.6250f, 9, 26}, // 49 + {0.781250f, 0.312500f, 0.847656f, 0.414063f, -0.8125f, -23.0625f, 14.3750f, 17, 26}, // 50 + {0.820313f, 0.527344f, 0.878906f, 0.628906f, -1.1875f, -23.0625f, 12.8750f, 15, 26}, // 51 + {0.406250f, 0.210938f, 0.476563f, 0.312500f, -1.6250f, -23.0625f, 13.8125f, 18, 26}, // 52 + {0.351563f, 0.316406f, 0.417969f, 0.417969f, -0.7500f, -23.0625f, 14.8750f, 17, 26}, // 53 + {0.628906f, 0.316406f, 0.695313f, 0.417969f, -0.7500f, -23.0625f, 14.9375f, 17, 26}, // 54 + {0.000000f, 0.507813f, 0.062500f, 0.609375f, -1.5000f, -23.0625f, 12.1250f, 16, 26}, // 55 + {0.859375f, 0.210938f, 0.929688f, 0.312500f, -0.8750f, -23.0625f, 15.5625f, 18, 26}, // 56 + {0.851563f, 0.316406f, 0.917969f, 0.417969f, -0.8750f, -23.0625f, 15.0000f, 17, 26}, // 57 + {0.660156f, 0.527344f, 0.691406f, 0.597656f, -0.3125f, -15.4375f, 6.5625f, 8, 18}, // 58 + {0.277344f, 0.128906f, 0.308594f, 0.218750f, -0.3125f, -15.4375f, 6.5625f, 8, 23}, // 59 + {0.335938f, 0.644531f, 0.398438f, 0.714844f, -0.5000f, -19.5000f, 14.7500f, 16, 18}, // 60 + {0.035156f, 0.707031f, 0.097656f, 0.753906f, -0.5000f, -16.3750f, 14.7500f, 16, 12}, // 61 + {0.660156f, 0.683594f, 0.722656f, 0.753906f, -0.5000f, -19.5000f, 14.7500f, 16, 18}, // 62 + {0.066406f, 0.515625f, 0.128906f, 0.617188f, -1.7500f, -23.0625f, 12.3750f, 16, 26}, // 63 + {0.191406f, 0.128906f, 0.273438f, 0.222656f, -0.8750f, -19.5625f, 18.5000f, 21, 24}, // 64 + {0.277344f, 0.222656f, 0.347656f, 0.324219f, -1.8125f, -23.0625f, 14.1875f, 18, 26}, // 65 + {0.222656f, 0.328125f, 0.289063f, 0.429688f, -0.5000f, -23.0625f, 15.0625f, 17, 26}, // 66 + {0.203125f, 0.539063f, 0.261719f, 0.640625f, -0.7500f, -23.0625f, 12.6875f, 15, 26}, // 67 + {0.140625f, 0.332031f, 0.207031f, 0.433594f, -0.5000f, -23.0625f, 15.5000f, 17, 26}, // 68 + {0.347656f, 0.539063f, 0.406250f, 0.640625f, -0.5000f, -23.0625f, 12.8750f, 15, 26}, // 69 + {0.132813f, 0.554688f, 0.191406f, 0.656250f, -0.5000f, -23.0625f, 11.8750f, 15, 26}, // 70 + {0.421875f, 0.343750f, 0.488281f, 0.445313f, -0.7500f, -23.0625f, 15.2500f, 17, 26}, // 71 + {0.699219f, 0.347656f, 0.765625f, 0.449219f, -0.5000f, -23.0625f, 15.8750f, 17, 26}, // 72 + {0.402344f, 0.695313f, 0.433594f, 0.796875f, -0.4375f, -23.0625f, 6.2500f, 8, 26}, // 73 + {0.527344f, 0.570313f, 0.585938f, 0.671875f, -1.7500f, -23.0625f, 11.8125f, 15, 26}, // 74 + {0.921875f, 0.355469f, 0.988281f, 0.457031f, -0.5000f, -23.0625f, 14.5000f, 17, 26}, // 75 + {0.222656f, 0.226563f, 0.277344f, 0.328125f, -0.5000f, -23.0625f, 11.1875f, 14, 26}, // 76 + {0.320313f, 0.117188f, 0.402344f, 0.218750f, -0.5000f, -23.0625f, 19.7500f, 21, 26}, // 77 + {0.492188f, 0.359375f, 0.558594f, 0.460938f, -0.5625f, -23.0625f, 15.8125f, 17, 26}, // 78 + {0.148438f, 0.226563f, 0.218750f, 0.328125f, -0.7500f, -23.0625f, 16.1250f, 18, 26}, // 79 + {0.562500f, 0.359375f, 0.628906f, 0.460938f, -0.5000f, -23.0625f, 14.5625f, 17, 26}, // 80 + {0.507813f, 0.125000f, 0.578125f, 0.234375f, -0.7500f, -23.0625f, 16.3125f, 18, 28}, // 81 + {0.000000f, 0.402344f, 0.066406f, 0.503906f, -0.5000f, -23.0625f, 15.4375f, 17, 26}, // 82 + {0.070313f, 0.410156f, 0.136719f, 0.511719f, -1.3125f, -23.0625f, 13.6875f, 17, 26}, // 83 + {0.753906f, 0.523438f, 0.816406f, 0.625000f, -1.7500f, -23.0625f, 12.3125f, 16, 26}, // 84 + {0.480469f, 0.238281f, 0.550781f, 0.339844f, -0.6250f, -23.0625f, 16.3125f, 18, 26}, // 85 + {0.554688f, 0.253906f, 0.625000f, 0.355469f, -1.8125f, -23.0625f, 13.8125f, 18, 26}, // 86 + {0.867188f, 0.000000f, 0.968750f, 0.101563f, -1.7500f, -23.0625f, 22.2500f, 26, 26}, // 87 + {0.769531f, 0.417969f, 0.835938f, 0.519531f, -1.5625f, -23.0625f, 13.5625f, 17, 26}, // 88 + {0.292969f, 0.421875f, 0.359375f, 0.523438f, -1.9375f, -23.0625f, 12.5625f, 17, 26}, // 89 + {0.281250f, 0.527344f, 0.343750f, 0.628906f, -1.0000f, -23.0625f, 13.0625f, 16, 26}, // 90 + {0.910156f, 0.460938f, 0.957031f, 0.601563f, 0.3750f, -26.9375f, 10.7500f, 12, 36}, // 91 + {0.140625f, 0.437500f, 0.199219f, 0.550781f, -2.0000f, -23.0625f, 9.1250f, 15, 29}, // 92 + {0.476563f, 0.464844f, 0.523438f, 0.605469f, -1.3750f, -26.9375f, 10.7500f, 12, 36}, // 93 + {0.726563f, 0.714844f, 0.781250f, 0.761719f, -1.1250f, -23.0625f, 11.4375f, 14, 12}, // 94 + {0.785156f, 0.718750f, 0.847656f, 0.742188f, -2.0000f, 0.0625f, 11.6250f, 16, 6}, // 95 + {0.566406f, 0.718750f, 0.617188f, 0.753906f, -0.5000f, -24.9375f, 11.9375f, 13, 9}, // 96 + {0.000000f, 0.613281f, 0.066406f, 0.695313f, -1.0000f, -18.6875f, 14.4375f, 17, 21}, // 97 + {0.632813f, 0.421875f, 0.699219f, 0.523438f, -0.5000f, -23.6875f, 15.3750f, 17, 26}, // 98 + {0.292969f, 0.328125f, 0.351563f, 0.410156f, -0.7500f, -18.6875f, 12.3750f, 15, 21}, // 99 + {0.839844f, 0.421875f, 0.906250f, 0.523438f, -0.7500f, -23.6875f, 15.3750f, 17, 26}, // 100 + {0.734375f, 0.628906f, 0.800781f, 0.710938f, -0.7500f, -18.6875f, 14.5000f, 17, 21}, // 101 + {0.136719f, 0.660156f, 0.179688f, 0.761719f, -0.5000f, -23.6875f, 8.1250f, 11, 26}, // 102 + {0.078125f, 0.191406f, 0.144531f, 0.300781f, -0.7500f, -18.6875f, 15.3750f, 17, 28}, // 103 + {0.210938f, 0.433594f, 0.277344f, 0.535156f, -0.5000f, -23.6875f, 15.6250f, 17, 26}, // 104 + {0.437500f, 0.695313f, 0.468750f, 0.796875f, -0.4375f, -23.6875f, 6.1875f, 8, 26}, // 105 + {0.484375f, 0.609375f, 0.527344f, 0.738281f, -3.3750f, -23.6875f, 6.6250f, 11, 33}, // 106 + {0.593750f, 0.527344f, 0.656250f, 0.628906f, -0.5000f, -23.6875f, 13.6250f, 16, 26}, // 107 + {0.000000f, 0.699219f, 0.031250f, 0.800781f, -0.5000f, -23.6875f, 6.0625f, 8, 26}, // 108 + {0.406250f, 0.125000f, 0.503906f, 0.207031f, -0.5000f, -18.6875f, 23.1875f, 25, 21}, // 109 + {0.265625f, 0.632813f, 0.332031f, 0.714844f, -0.5000f, -18.6875f, 15.6250f, 17, 21}, // 110 + {0.660156f, 0.597656f, 0.730469f, 0.679688f, -0.7500f, -18.6875f, 15.5625f, 18, 21}, // 111 + {0.789063f, 0.199219f, 0.855469f, 0.308594f, -0.5000f, -18.6875f, 15.3750f, 17, 28}, // 112 + {0.656250f, 0.203125f, 0.722656f, 0.312500f, -0.7500f, -18.6875f, 15.3750f, 17, 28}, // 113 + {0.351563f, 0.222656f, 0.398438f, 0.304688f, -0.6250f, -18.6875f, 9.6875f, 12, 21}, // 114 + {0.070313f, 0.621094f, 0.132813f, 0.703125f, -1.1250f, -18.6875f, 13.6250f, 16, 21}, // 115 + {0.195313f, 0.644531f, 0.242188f, 0.746094f, -0.6250f, -23.0625f, 8.9375f, 12, 26}, // 116 + {0.589844f, 0.632813f, 0.656250f, 0.714844f, -0.6250f, -18.6875f, 15.5000f, 17, 21}, // 117 + {0.882813f, 0.605469f, 0.953125f, 0.687500f, -1.7500f, -18.6875f, 14.3125f, 18, 21}, // 118 + {0.683594f, 0.117188f, 0.785156f, 0.199219f, -1.6875f, -18.6875f, 22.4375f, 26, 21}, // 119 + {0.410156f, 0.609375f, 0.480469f, 0.691406f, -1.7500f, -18.6875f, 13.8125f, 18, 21}, // 120 + {0.582031f, 0.140625f, 0.652344f, 0.250000f, -1.7500f, -18.6875f, 14.0000f, 18, 28}, // 121 + {0.804688f, 0.632813f, 0.863281f, 0.714844f, -1.0000f, -18.6875f, 12.7500f, 15, 21}, // 122 + {0.726563f, 0.203125f, 0.777344f, 0.343750f, -0.6875f, -26.9375f, 10.9375f, 13, 36}, // 123 + {0.960938f, 0.460938f, 0.988281f, 0.597656f, 0.6875f, -26.3125f, 7.9375f, 7, 35}, // 124 + {0.933594f, 0.210938f, 0.984375f, 0.351563f, -1.3750f, -26.9375f, 10.9375f, 13, 36}, // 125 + {0.296875f, 0.718750f, 0.359375f, 0.750000f, -0.5000f, -14.1875f, 14.7500f, 16, 8}, // 126 + {0.582031f, 0.000000f, 0.679688f, 0.136719f, -0.5000f, -25.2500f, 23.1250f, 25, 35}, // 127 + }, + "UBUNTU24", // name +}; +const AtlasFont *ppge_fonts[1] = { + &font_UBUNTU24, +}; +const AtlasImage ppge_images[6] = { + {0.458984f, 0.001953f, 0.576172f, 0.119141f, 31, 31, "I_CROSS"}, + {0.193359f, 0.001953f, 0.314453f, 0.123047f, 32, 32, "I_CIRCLE"}, + {0.685547f, 0.001953f, 0.794922f, 0.111328f, 29, 29, "I_SQUARE"}, + {0.322266f, 0.001953f, 0.451172f, 0.111328f, 34, 29, "I_TRIANGLE"}, + {0.802734f, 0.001953f, 0.861328f, 0.193359f, 16, 50, "I_BUTTON"}, + {0.001953f, 0.001953f, 0.185547f, 0.185547f, 48, 48, "I_LOGO"}, +}; +const Atlas ppge_atlas = { + "ppge_atlas.zim", + ppge_fonts, 1, + ppge_images, 6, +}; diff --git a/Core/Util/ppge_atlas.h b/Core/Util/ppge_atlas.h new file mode 100644 index 0000000000..0aa2a88fcf --- /dev/null +++ b/Core/Util/ppge_atlas.h @@ -0,0 +1,20 @@ +// Header generated by atlastool from ppge_atlasscript.txt (hrydgard@gmail.com) + +#pragma once +#include "gfx/texture_atlas.h" + +// FONTS_ppge +#define UBUNTU24 0 + + +// IMAGES_ppge +#define I_CROSS 0 +#define I_CIRCLE 1 +#define I_SQUARE 2 +#define I_TRIANGLE 3 +#define I_BUTTON 4 +#define I_LOGO 5 + + +extern const Atlas ppge_atlas; +extern const AtlasImage ppge_images[6]; diff --git a/GPU/GLES/DisplayListInterpreter.cpp b/GPU/GLES/DisplayListInterpreter.cpp index d927f94326..4e02d04609 100644 --- a/GPU/GLES/DisplayListInterpreter.cpp +++ b/GPU/GLES/DisplayListInterpreter.cpp @@ -160,7 +160,7 @@ void drawBezier(int ucount, int vcount) } LinkedShader *linkedShader = shaderManager.ApplyShader(); - TransformAndDrawPrim(Memory::GetPointer(gstate.vertexAddr), &indices[0], GE_PRIM_TRIANGLES, 3 * 3 * 6, linkedShader, customUV, GE_VTYPE_IDX_16BIT); + TransformAndDrawPrim(Memory::GetPointer(gstate_c.vertexAddr), &indices[0], GE_PRIM_TRIANGLES, 3 * 3 * 6, linkedShader, customUV, GE_VTYPE_IDX_16BIT); } @@ -256,17 +256,17 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) switch (cmd) { case GE_CMD_BASE: - DEBUG_LOG(G3D,"DL BASE: %06x", data); + DEBUG_LOG(G3D,"DL BASE: %06x", data & 0xFFFFFF); break; case GE_CMD_VADDR: /// <<8???? - gstate.vertexAddr = ((gstate.base & 0x00FF0000) << 8)|data; - DEBUG_LOG(G3D,"DL VADDR: %06x", gstate.vertexAddr); + gstate_c.vertexAddr = ((gstate.base & 0x00FF0000) << 8)|data; + DEBUG_LOG(G3D,"DL VADDR: %06x", gstate_c.vertexAddr); break; case GE_CMD_IADDR: - gstate.indexAddr = ((gstate.base & 0x00FF0000) << 8)|data; - DEBUG_LOG(G3D,"DL IADDR: %06x", gstate.indexAddr); + gstate_c.indexAddr = ((gstate.base & 0x00FF0000) << 8)|data; + DEBUG_LOG(G3D,"DL IADDR: %06x", gstate_c.indexAddr); break; case GE_CMD_PRIM: @@ -282,15 +282,15 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) "TRIANGLE_FAN=5,", "RECTANGLES=6,", }; - DEBUG_LOG(G3D, "DL DrawPrim type: %s count: %i vaddr= %08x, iaddr= %08x", type<7 ? types[type] : "INVALID", count, gstate.vertexAddr, gstate.indexAddr); + DEBUG_LOG(G3D, "DL DrawPrim type: %s count: %i vaddr= %08x, iaddr= %08x", type<7 ? types[type] : "INVALID", count, gstate_c.vertexAddr, gstate_c.indexAddr); LinkedShader *linkedShader = shaderManager.ApplyShader(); // TODO: Split this so that we can collect sequences of primitives, can greatly speed things up // on platforms where draw calls are expensive like mobile and D3D - void *verts = Memory::GetPointer(gstate.vertexAddr); + void *verts = Memory::GetPointer(gstate_c.vertexAddr); void *inds = 0; if ((gstate.vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) - inds = Memory::GetPointer(gstate.indexAddr); + inds = Memory::GetPointer(gstate_c.indexAddr); TransformAndDrawPrim(verts, inds, type, count, linkedShader); } break; @@ -349,7 +349,8 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) int behaviour = (data >> 16) & 0xFF; int signal = data & 0xFFFF; - __TriggerInterruptWithArg(PSP_GE_INTR, PSP_GE_SUBINTR_SIGNAL, signal); + if (interruptsEnabled_) + __TriggerInterruptWithArg(PSP_GE_INTR, PSP_GE_SUBINTR_SIGNAL, signal); } break; @@ -389,7 +390,8 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) case GE_CMD_FINISH: DEBUG_LOG(G3D,"DL CMD FINISH"); - __TriggerInterruptWithArg(PSP_GE_INTR, PSP_GE_SUBINTR_FINISH, 0); + if (interruptsEnabled_) + __TriggerInterruptWithArg(PSP_GE_INTR, PSP_GE_SUBINTR_FINISH, 0); break; case GE_CMD_END: @@ -407,7 +409,8 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) } // This should generate a Reading Ended interrupt - // __TriggerInterrupt(PSP_GE_INTR); + // if (interruptsEnabled_) + // __TriggerInterrupt(PSP_GE_INTR); break; @@ -466,23 +469,23 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) break; case GE_CMD_TEXSCALEU: - gstate.uScale = getFloat24(data); - DEBUG_LOG(G3D, "DL Texture U Scale: %f", gstate.uScale); + gstate_c.uScale = getFloat24(data); + DEBUG_LOG(G3D, "DL Texture U Scale: %f", gstate_c.uScale); break; case GE_CMD_TEXSCALEV: - gstate.vScale = getFloat24(data); - DEBUG_LOG(G3D, "DL Texture V Scale: %f", gstate.vScale); + gstate_c.vScale = getFloat24(data); + DEBUG_LOG(G3D, "DL Texture V Scale: %f", gstate_c.vScale); break; case GE_CMD_TEXOFFSETU: - gstate.uOff = getFloat24(data); - DEBUG_LOG(G3D, "DL Texture U Offset: %f", gstate.uOff); + gstate_c.uOff = getFloat24(data); + DEBUG_LOG(G3D, "DL Texture U Offset: %f", gstate_c.uOff); break; case GE_CMD_TEXOFFSETV: - gstate.vOff = getFloat24(data); - DEBUG_LOG(G3D, "DL Texture V Offset: %f", gstate.vOff); + gstate_c.vOff = getFloat24(data); + DEBUG_LOG(G3D, "DL Texture V Offset: %f", gstate_c.vOff); break; case GE_CMD_SCISSOR1: @@ -526,7 +529,7 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) break; case GE_CMD_TEXADDR0: - gstate.textureChanged = true; + gstate_c.textureChanged = true; case GE_CMD_TEXADDR1: case GE_CMD_TEXADDR2: case GE_CMD_TEXADDR3: @@ -538,7 +541,7 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) break; case GE_CMD_TEXBUFWIDTH0: - gstate.textureChanged = true; + gstate_c.textureChanged = true; case GE_CMD_TEXBUFWIDTH1: case GE_CMD_TEXBUFWIDTH2: case GE_CMD_TEXBUFWIDTH3: @@ -558,16 +561,12 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) break; case GE_CMD_LOADCLUT: + // This could be used to "dirty" textures with clut. { - u32 clutAttr = ((gstate.clutaddrupper & 0xFF0000)<<8) | (gstate.clutaddr & 0xFFFFFF); - if (clutAttr) + u32 clutAddr = ((gstate.clutaddrupper & 0xFF0000)<<8) | (gstate.clutaddr & 0xFFFFFF); + if (clutAddr) { - u16 *clut = (u16*)Memory::GetPointer(clutAttr); - if (clut) { - int numColors = 16 * (data&0x3F); - memcpy(&gstate.paletteMem[0], clut, numColors * 2); - } - DEBUG_LOG(G3D,"DL Clut load: %i palettes", data); + DEBUG_LOG(G3D,"DL Clut load: %08x", clutAddr); } else { @@ -630,9 +629,9 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) } case GE_CMD_TEXSIZE0: - gstate.textureChanged=true; - gstate.curTextureWidth = 1 << (gstate.texsize[0] & 0xf); - gstate.curTextureHeight = 1 << ((gstate.texsize[0]>>8) & 0xf); + gstate_c.textureChanged=true; + gstate_c.curTextureWidth = 1 << (gstate.texsize[0] & 0xf); + gstate_c.curTextureHeight = 1 << ((gstate.texsize[0]>>8) & 0xf); //fall thru - ignoring the mipmap sizes for now case GE_CMD_TEXSIZE1: case GE_CMD_TEXSIZE2: @@ -707,7 +706,7 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) int c = n % 3; float val = getFloat24(data); DEBUG_LOG(G3D,"DL Light %i %c pos: %f", l, c+'X', val); - gstate.lightpos[l][c] = val; + gstate_c.lightpos[l][c] = val; } break; @@ -721,7 +720,7 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) int c = n % 3; float val = getFloat24(data); DEBUG_LOG(G3D,"DL Light %i %c dir: %f", l, c+'X', val); - gstate.lightdir[l][c] = val; + gstate_c.lightdir[l][c] = val; } break; @@ -735,7 +734,7 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) int c = n % 3; float val = getFloat24(data); DEBUG_LOG(G3D,"DL Light %i %c att: %f", l, c+'X', val); - gstate.lightatt[l][c] = val; + gstate_c.lightatt[l][c] = val; } break; @@ -750,9 +749,9 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) int l = (cmd - GE_CMD_LAC0) / 3; int t = (cmd - GE_CMD_LAC0) % 3; - gstate.lightColor[t][l].r = r; - gstate.lightColor[t][l].g = g; - gstate.lightColor[t][l].b = b; + gstate_c.lightColor[t][l].r = r; + gstate_c.lightColor[t][l].g = g; + gstate_c.lightColor[t][l].b = b; } break; @@ -781,9 +780,9 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) break; case GE_CMD_PATCHDIVISION: - gstate.patch_div_s = data & 0xFF; - gstate.patch_div_t = (data >> 8) & 0xFF; - DEBUG_LOG(G3D, "DL Patch subdivision: %i x %i", gstate.patch_div_s, gstate.patch_div_t); + gstate_c.patch_div_s = data & 0xFF; + gstate_c.patch_div_t = (data >> 8) & 0xFF; + DEBUG_LOG(G3D, "DL Patch subdivision: %i x %i", gstate_c.patch_div_s, gstate_c.patch_div_t); break; case GE_CMD_MATERIALUPDATE: @@ -908,7 +907,7 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) int index = cmd - GE_CMD_MORPHWEIGHT0; float weight = getFloat24(data); DEBUG_LOG(G3D,"DL MorphWeight %i = %f", index, weight); - gstate.morphWeights[index] = weight; + gstate_c.morphWeights[index] = weight; } break; diff --git a/GPU/GLES/DisplayListInterpreter.h b/GPU/GLES/DisplayListInterpreter.h index 179655e519..6a67b42f19 100644 --- a/GPU/GLES/DisplayListInterpreter.h +++ b/GPU/GLES/DisplayListInterpreter.h @@ -24,11 +24,16 @@ class ShaderManager; class GLES_GPU : public GPUInterface { public: + GLES_GPU() : interruptsEnabled_(true) {} virtual u32 EnqueueList(u32 listpc, u32 stall); virtual void UpdateStall(int listid, u32 newstall); virtual void ExecuteOp(u32 op, u32 diff); virtual bool InterpretList(); virtual void DrawSync(int mode); + virtual void EnableInterrupts(bool enable) { + interruptsEnabled_ = enable; + } private: bool ProcessDLQueue(); + bool interruptsEnabled_; }; diff --git a/GPU/GLES/Framebuffer.cpp b/GPU/GLES/Framebuffer.cpp index d3477958a1..0caf6b5267 100644 --- a/GPU/GLES/Framebuffer.cpp +++ b/GPU/GLES/Framebuffer.cpp @@ -108,7 +108,8 @@ void DisplayDrawer_Init() glUniformMatrix4fv(draw2dprogram->u_viewproj, 1, GL_FALSE, ortho.getReadPtr()); glsl_unbind(); - // And an initial clear. + // And an initial clear. We don't clear per frame as the games are supposed to handle that + // by themselves. glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); diff --git a/GPU/GLES/ShaderManager.cpp b/GPU/GLES/ShaderManager.cpp index ec0e375ec9..020e176596 100644 --- a/GPU/GLES/ShaderManager.cpp +++ b/GPU/GLES/ShaderManager.cpp @@ -37,6 +37,9 @@ #include "TransformPipeline.h" Shader::Shader(const char *code, uint32_t shaderType) { +#ifdef _DEBUG + source_ = code; +#endif #ifdef _WIN32 // OutputDebugString(code); #endif diff --git a/GPU/GLES/ShaderManager.h b/GPU/GLES/ShaderManager.h index d4709ee46b..674c949377 100644 --- a/GPU/GLES/ShaderManager.h +++ b/GPU/GLES/ShaderManager.h @@ -72,8 +72,9 @@ enum struct Shader { Shader(const char *code, uint32_t shaderType); - // const char *source; uint32_t shader; +private: + std::string source_; }; diff --git a/GPU/GLES/TextureCache.cpp b/GPU/GLES/TextureCache.cpp index 6cc852b6ce..90e4fa3cc7 100644 --- a/GPU/GLES/TextureCache.cpp +++ b/GPU/GLES/TextureCache.cpp @@ -579,8 +579,8 @@ void PSPSetTexture() u32 w = 1 << (gstate.texsize[0] & 0xf); u32 h = 1 << ((gstate.texsize[0]>>8) & 0xf); - gstate.curTextureHeight=h; - gstate.curTextureWidth=w; + gstate_c.curTextureWidth=w; + gstate_c.curTextureHeight=h; GLenum dstFmt = 0; u32 texByteAlign = 1; diff --git a/GPU/GLES/TransformPipeline.cpp b/GPU/GLES/TransformPipeline.cpp index ec3291ef23..05d84640e8 100644 --- a/GPU/GLES/TransformPipeline.cpp +++ b/GPU/GLES/TransformPipeline.cpp @@ -145,9 +145,9 @@ void Lighter::Light(float colorOut0[4], float colorOut1[4], const float colorIn[ Vec3 toLight; if (type == GE_LIGHTTYPE_DIRECTIONAL) - toLight = Vec3(gstate.lightpos[l]); // lightdir is for spotlights + toLight = Vec3(gstate_c.lightpos[l]); // lightdir is for spotlights else - toLight = Vec3(gstate.lightpos[l]) - pos; + toLight = Vec3(gstate_c.lightpos[l]) - pos; bool doSpecular = (comp != GE_LIGHTCOMP_ONLYDIFFUSE); bool poweredDiffuse = comp == GE_LIGHTCOMP_BOTHWITHPOWDIFFUSE; @@ -156,7 +156,7 @@ void Lighter::Light(float colorOut0[4], float colorOut1[4], const float colorIn[ if (type != GE_LIGHTTYPE_DIRECTIONAL) { float distance = toLight.Normalize(); - lightScale = 1.0f / (gstate.lightatt[l][0] + gstate.lightatt[l][1]*distance + gstate.lightatt[l][2]*distance*distance); + lightScale = 1.0f / (gstate_c.lightatt[l][0] + gstate_c.lightatt[l][1]*distance + gstate_c.lightatt[l][2]*distance*distance); if (lightScale > 1.0f) lightScale = 1.0f; } @@ -168,7 +168,7 @@ void Lighter::Light(float colorOut0[4], float colorOut1[4], const float colorIn[ if (poweredDiffuse) dot = powf(dot, specCoef_); - Color4 diff = (gstate.lightColor[1][l] * *diffuse) * (dot * lightScale); + Color4 diff = (gstate_c.lightColor[1][l] * *diffuse) * (dot * lightScale); // Real PSP specular Vec3 toViewer(0,0,1); @@ -184,13 +184,13 @@ void Lighter::Light(float colorOut0[4], float colorOut1[4], const float colorIn[ dot = halfVec * norm; if (dot >= 0) { - lightSum1 += (gstate.lightColor[2][l] * *specular * (powf(dot, specCoef_)*lightScale)); + lightSum1 += (gstate_c.lightColor[2][l] * *specular * (powf(dot, specCoef_)*lightScale)); } } dots[l] = dot; if (gstate.lightEnable[l] & 1) { - lightSum0 += gstate.lightColor[0][l] * *ambient + diff; + lightSum0 += gstate_c.lightColor[0][l] * *ambient + diff; } } @@ -216,9 +216,9 @@ void TransformAndDrawPrim(void *verts, void *inds, int prim, int vertexCount, Li bool useTexCoord = false; // Check if anything needs updating - if (gstate.textureChanged) + if (gstate_c.textureChanged) { - if (gstate.textureMapEnable && !(gstate.clearmode & 1)) + if ((gstate.textureMapEnable & 1) && !(gstate.clearmode & 1)) { PSPSetTexture(); useTexCoord = true; @@ -356,16 +356,16 @@ void TransformAndDrawPrim(void *verts, void *inds, int prim, int vertexCount, Li } if (customUV) { - uv[0] = customUV[index * 2 + 0]*gstate.uScale + gstate.uOff; - uv[1] = customUV[index * 2 + 1]*gstate.vScale + gstate.vOff; + uv[0] = customUV[index * 2 + 0]*gstate_c.uScale + gstate_c.uOff; + uv[1] = customUV[index * 2 + 1]*gstate_c.vScale + gstate_c.vOff; } else { // Perform texture coordinate generation after the transform and lighting - one style of UV depends on lights. switch (gstate.texmapmode & 0x3) { case 0: // UV mapping // Texture scale/offset is only performed in this mode. - uv[0] = decoded[index].uv[0]*gstate.uScale + gstate.uOff; - uv[1] = decoded[index].uv[1]*gstate.vScale + gstate.vOff; + uv[0] = decoded[index].uv[0]*gstate_c.uScale + gstate_c.uOff; + uv[1] = decoded[index].uv[1]*gstate_c.vScale + gstate_c.vOff; break; case 1: { diff --git a/GPU/GLES/VertexDecoder.cpp b/GPU/GLES/VertexDecoder.cpp index ef6104fd63..380567b1f5 100644 --- a/GPU/GLES/VertexDecoder.cpp +++ b/GPU/GLES/VertexDecoder.cpp @@ -121,8 +121,9 @@ void VertexDecoder::SetVertexType(u32 fmt) void VertexDecoder::DecodeVerts(DecodedVertex *decoded, const void *verts, const void *inds, int prim, int count, int *indexLowerBound, int *indexUpperBound) const { + // TODO: Remove if (morphcount == 1) - gstate.morphWeights[0] = 1.0f; + gstate_c.morphWeights[0] = 1.0f; char *ptr = (char *)verts; @@ -211,8 +212,8 @@ void VertexDecoder::DecodeVerts(DecodedVertex *decoded, const void *verts, const const u16 *uvdata = (const u16*)(ptr + tcoff); if (throughmode) { - uv[0] = (float)uvdata[0] / (float)(gstate.curTextureWidth); - uv[1] = (float)uvdata[1] / (float)(gstate.curTextureHeight); + uv[0] = (float)uvdata[0] / (float)(gstate_c.curTextureWidth); + uv[1] = (float)uvdata[1] / (float)(gstate_c.curTextureHeight); } else { @@ -273,7 +274,10 @@ void VertexDecoder::DecodeVerts(DecodedVertex *decoded, const void *verts, const break; default: - c[0]=255; c[1]=255; c[2]=255; c[3]=255; + c[0] = 255; + c[1] = 255; + c[2] = 255; + c[3] = 255; break; } @@ -281,7 +285,7 @@ void VertexDecoder::DecodeVerts(DecodedVertex *decoded, const void *verts, const memset(normal, 0, sizeof(float)*3); for (int n = 0; n < morphcount; n++) { - float multiplier = gstate.morphWeights[n]; + float multiplier = gstate_c.morphWeights[n]; if (gstate.reversenormals & 0xFFFFFF) { multiplier = -multiplier; } @@ -358,7 +362,7 @@ void VertexDecoder::DecodeVerts(DecodedVertex *decoded, const void *verts, const { const float *fv = (const float*)(ptr + onesize_*n + posoff); for (int j = 0; j < 3; j++) - v[j] += fv[j] * gstate.morphWeights[n]; + v[j] += fv[j] * gstate_c.morphWeights[n]; } break; @@ -368,7 +372,7 @@ void VertexDecoder::DecodeVerts(DecodedVertex *decoded, const void *verts, const if (throughmode) multiplier = 1.0f; const short *sv = (const short*)(ptr + onesize_*n + posoff); for (int j = 0; j < 3; j++) - v[j] += (sv[j] * multiplier) * gstate.morphWeights[n]; + v[j] += (sv[j] * multiplier) * gstate_c.morphWeights[n]; } break; @@ -376,7 +380,7 @@ void VertexDecoder::DecodeVerts(DecodedVertex *decoded, const void *verts, const { const s8 *sv = (const s8*)(ptr + onesize_*n + posoff); for (int j = 0; j < 3; j++) - v[j] += (sv[j] / 127.f) * gstate.morphWeights[n]; + v[j] += (sv[j] / 127.f) * gstate_c.morphWeights[n]; } break; diff --git a/GPU/GPUInterface.h b/GPU/GPUInterface.h index 5e373b5d93..049f720833 100644 --- a/GPU/GPUInterface.h +++ b/GPU/GPUInterface.h @@ -27,4 +27,7 @@ public: virtual void ExecuteOp(u32 op, u32 diff) = 0; virtual bool InterpretList() = 0; virtual void DrawSync(int mode) = 0; + + // Internal hack to avoid interrupts from "PPGe" drawing (utility UI, etc) + virtual void EnableInterrupts(bool enable) = 0; }; diff --git a/GPU/GPUState.cpp b/GPU/GPUState.cpp index 801aa07f89..9877395ab3 100644 --- a/GPU/GPUState.cpp +++ b/GPU/GPUState.cpp @@ -36,7 +36,9 @@ #include "../Core/System.h" GPUgstate gstate; +GPUStateCache gstate_c; GPUInterface *gpu; +GPUStatistics gpuStats; void InitGfxState() { @@ -53,9 +55,9 @@ void InitGfxState() 0,0,0,}; static const float identity4x4[16] = {1,0,0,0, - 0,1,0,0, - 0,0,1,0, - 0,0,0,1}; + 0,1,0,0, + 0,0,1,0, + 0,0,0,1}; memcpy(gstate.worldMatrix, identity4x3, 12 * sizeof(float)); memcpy(gstate.viewMatrix, identity4x3, 12 * sizeof(float)); @@ -85,9 +87,12 @@ void ShutdownGfxState() // or saved the context and has reloaded it, call this function. void ReapplyGfxState() { + if (!gpu) + return; // ShaderManager_DirtyShader(); // The commands are embedded in the command memory so we can just reexecute the words. Convenient. // To be safe we pass 0xFFFFFFF as the diff. + /* gpu->ExecuteOp(gstate.cmdmem[GE_CMD_ALPHABLENDENABLE], 0xFFFFFFFF); gpu->ExecuteOp(gstate.cmdmem[GE_CMD_ALPHATESTENABLE], 0xFFFFFFFF); gpu->ExecuteOp(gstate.cmdmem[GE_CMD_BLENDMODE], 0xFFFFFFFF); @@ -97,4 +102,24 @@ void ReapplyGfxState() gpu->ExecuteOp(gstate.cmdmem[GE_CMD_CULLFACEENABLE], 0xFFFFFFFF); gpu->ExecuteOp(gstate.cmdmem[GE_CMD_SCISSOR1], 0xFFFFFFFF); gpu->ExecuteOp(gstate.cmdmem[GE_CMD_SCISSOR2], 0xFFFFFFFF); + */ + for (int i = GE_CMD_VERTEXTYPE; i < GE_CMD_BONEMATRIXNUMBER; i++) + { + gpu->ExecuteOp(gstate.cmdmem[i], 0xFFFFFFFF); + } + + // Can't write to bonematrixnumber here + + for (int i = GE_CMD_MORPHWEIGHT0; i < GE_CMD_PATCHFACING; i++) + { + gpu->ExecuteOp(gstate.cmdmem[i], 0xFFFFFFFF); + } + + // There are a few here in the middle that we shouldn't execute... + + for (int i = GE_CMD_VIEWPORTX1; i < GE_CMD_TRANSFERSTART; i++) + { + gpu->ExecuteOp(gstate.cmdmem[i], 0xFFFFFFFF); + } + // TODO: there's more... } diff --git a/GPU/GPUState.h b/GPU/GPUState.h index 312f24d13d..864d523316 100644 --- a/GPU/GPUState.h +++ b/GPU/GPUState.h @@ -72,8 +72,6 @@ struct Color4 struct GPUgstate { - u16 paletteMem[256*2]; - union { u32 cmdmem[256]; @@ -230,6 +228,20 @@ struct GPUgstate }; }; + float worldMatrix[12]; + float viewMatrix[12]; + float projMatrix[16]; + float tgenMatrix[12]; + float boneMatrix[8*12]; +}; + +struct GPUStateCache +{ + // Real data in the context ends here + // The rest is cached simplified/converted data for fast access. + // What we have here still fits into 512 words, but just barely so we should + // in the future just recompute the below on an sceGeRestoreContext(). + u32 vertexAddr; u32 indexAddr; @@ -249,17 +261,22 @@ struct GPUgstate u32 curTextureWidth; u32 curTextureHeight; +}; - float worldMatrix[12]; - float viewMatrix[12]; - float projMatrix[16]; - float tgenMatrix[12]; - float boneMatrix[8*12]; +// TODO: Implement support for these. +struct GPUStatistics +{ + // Per frame statistics + int numDrawCalls; + int numTextureSwitches; + int numShaderSwitches; + + // Total statistics + int numFrames; }; void InitGfxState(); void ShutdownGfxState(); - void ReapplyGfxState(); // PSP uses a curious 24-bit float - it's basically the top 24 bits of a regular IEEE754 32-bit float. @@ -282,4 +299,6 @@ inline unsigned int toFloat24(float f) { class GPUInterface; extern GPUgstate gstate; -extern GPUInterface *gpu; \ No newline at end of file +extern GPUStateCache gstate_c; +extern GPUInterface *gpu; +extern GPUStatistics gpuStats; \ No newline at end of file diff --git a/GPU/Null/NullGpu.cpp b/GPU/Null/NullGpu.cpp index c9cff19f3c..0445eecff4 100644 --- a/GPU/Null/NullGpu.cpp +++ b/GPU/Null/NullGpu.cpp @@ -121,13 +121,13 @@ void NullGPU::ExecuteOp(u32 op, u32 diff) break; case GE_CMD_VADDR: /// <<8???? - gstate.vertexAddr = ((gstate.base & 0x00FF0000) << 8)|data; - DEBUG_LOG(G3D,"DL VADDR: %06x", gstate.vertexAddr); + gstate_c.vertexAddr = ((gstate.base & 0x00FF0000) << 8)|data; + DEBUG_LOG(G3D,"DL VADDR: %06x", gstate_c.vertexAddr); break; case GE_CMD_IADDR: - gstate.indexAddr = ((gstate.base & 0x00FF0000) << 8)|data; - DEBUG_LOG(G3D,"DL IADDR: %06x", gstate.indexAddr); + gstate_c.indexAddr = ((gstate.base & 0x00FF0000) << 8)|data; + DEBUG_LOG(G3D,"DL IADDR: %06x", gstate_c.indexAddr); break; case GE_CMD_PRIM: @@ -143,7 +143,7 @@ void NullGPU::ExecuteOp(u32 op, u32 diff) "TRIANGLE_FAN=5,", "RECTANGLES=6,", }; - DEBUG_LOG(G3D, "DL DrawPrim type: %s count: %i vaddr= %08x, iaddr= %08x", type<7 ? types[type] : "INVALID", count, gstate.vertexAddr, gstate.indexAddr); + DEBUG_LOG(G3D, "DL DrawPrim type: %s count: %i vaddr= %08x, iaddr= %08x", type<7 ? types[type] : "INVALID", count, gstate_c.vertexAddr, gstate_c.indexAddr); } break; @@ -200,7 +200,8 @@ void NullGPU::ExecuteOp(u32 op, u32 diff) int behaviour = (data >> 16) & 0xFF; int signal = data & 0xFFFF; - __TriggerInterruptWithArg(PSP_GE_INTR, PSP_GE_SUBINTR_SIGNAL, signal); + if (interruptsEnabled_) + __TriggerInterruptWithArg(PSP_GE_INTR, PSP_GE_SUBINTR_SIGNAL, signal); } break; @@ -230,7 +231,8 @@ void NullGPU::ExecuteOp(u32 op, u32 diff) case GE_CMD_FINISH: DEBUG_LOG(G3D,"DL CMD FINISH"); - __TriggerInterruptWithArg(PSP_GE_INTR, PSP_GE_SUBINTR_FINISH, 0); + if (interruptsEnabled_) + __TriggerInterruptWithArg(PSP_GE_INTR, PSP_GE_SUBINTR_FINISH, 0); break; case GE_CMD_END: @@ -248,6 +250,7 @@ void NullGPU::ExecuteOp(u32 op, u32 diff) } // This should generate a Reading Ended interrupt + // if (interruptsEnabled_) // __TriggerInterrupt(PSP_GE_INTR); break; @@ -305,23 +308,23 @@ void NullGPU::ExecuteOp(u32 op, u32 diff) break; case GE_CMD_TEXSCALEU: - gstate.uScale = getFloat24(data); - DEBUG_LOG(G3D, "DL Texture U Scale: %f", gstate.uScale); + gstate_c.uScale = getFloat24(data); + DEBUG_LOG(G3D, "DL Texture U Scale: %f", gstate_c.uScale); break; case GE_CMD_TEXSCALEV: - gstate.vScale = getFloat24(data); - DEBUG_LOG(G3D, "DL Texture V Scale: %f", gstate.vScale); + gstate_c.vScale = getFloat24(data); + DEBUG_LOG(G3D, "DL Texture V Scale: %f", gstate_c.vScale); break; case GE_CMD_TEXOFFSETU: - gstate.uOff = getFloat24(data); - DEBUG_LOG(G3D, "DL Texture U Offset: %f", gstate.uOff); + gstate_c.uOff = getFloat24(data); + DEBUG_LOG(G3D, "DL Texture U Offset: %f", gstate_c.uOff); break; case GE_CMD_TEXOFFSETV: - gstate.vOff = getFloat24(data); - DEBUG_LOG(G3D, "DL Texture V Offset: %f", gstate.vOff); + gstate_c.vOff = getFloat24(data); + DEBUG_LOG(G3D, "DL Texture V Offset: %f", gstate_c.vOff); break; case GE_CMD_SCISSOR1: @@ -365,7 +368,7 @@ void NullGPU::ExecuteOp(u32 op, u32 diff) break; case GE_CMD_TEXADDR0: - gstate.textureChanged=true; + gstate_c.textureChanged=true; case GE_CMD_TEXADDR1: case GE_CMD_TEXADDR2: case GE_CMD_TEXADDR3: @@ -377,7 +380,7 @@ void NullGPU::ExecuteOp(u32 op, u32 diff) break; case GE_CMD_TEXBUFWIDTH0: - gstate.textureChanged=true; + gstate_c.textureChanged=true; case GE_CMD_TEXBUFWIDTH1: case GE_CMD_TEXBUFWIDTH2: case GE_CMD_TEXBUFWIDTH3: @@ -397,16 +400,12 @@ void NullGPU::ExecuteOp(u32 op, u32 diff) break; case GE_CMD_LOADCLUT: + // This could be used to "dirty" textures with clut. { - u32 clutAttr = ((gstate.clutaddrupper & 0xFF0000)<<8) | (gstate.clutaddr & 0xFFFFFF); - if (clutAttr) + u32 clutAddr = ((gstate.clutaddrupper & 0xFF0000)<<8) | (gstate.clutaddr & 0xFFFFFF); + if (clutAddr) { - u16 *clut = (u16*)Memory::GetPointer(clutAttr); - if (clut) { - int numColors = 16 * (data&0x3F); - memcpy(&gstate.paletteMem[0], clut, numColors * 2); - } - DEBUG_LOG(G3D,"DL Clut load: %i palettes", data); + DEBUG_LOG(G3D,"DL Clut load: %08x", clutAddr); } else { @@ -416,7 +415,7 @@ void NullGPU::ExecuteOp(u32 op, u32 diff) } break; -// case GE_CMD_TRANSFERSRC: +//case GE_CMD_TRANSFERSRC: case GE_CMD_TRANSFERSRCW: { @@ -469,9 +468,9 @@ void NullGPU::ExecuteOp(u32 op, u32 diff) } case GE_CMD_TEXSIZE0: - gstate.textureChanged=true; - gstate.curTextureWidth = 1 << (gstate.texsize[0] & 0xf); - gstate.curTextureHeight = 1 << ((gstate.texsize[0]>>8) & 0xf); + gstate_c.textureChanged=true; + gstate_c.curTextureWidth = 1 << (gstate.texsize[0] & 0xf); + gstate_c.curTextureHeight = 1 << ((gstate.texsize[0]>>8) & 0xf); //fall thru - ignoring the mipmap sizes for now case GE_CMD_TEXSIZE1: case GE_CMD_TEXSIZE2: @@ -546,7 +545,7 @@ void NullGPU::ExecuteOp(u32 op, u32 diff) int c = n % 3; float val = getFloat24(data); DEBUG_LOG(G3D,"DL Light %i %c pos: %f", l, c+'X', val); - gstate.lightpos[l][c] = val; + gstate_c.lightpos[l][c] = val; } break; @@ -560,7 +559,7 @@ void NullGPU::ExecuteOp(u32 op, u32 diff) int c = n % 3; float val = getFloat24(data); DEBUG_LOG(G3D,"DL Light %i %c dir: %f", l, c+'X', val); - gstate.lightdir[l][c] = val; + gstate_c.lightdir[l][c] = val; } break; @@ -574,7 +573,7 @@ void NullGPU::ExecuteOp(u32 op, u32 diff) int c = n % 3; float val = getFloat24(data); DEBUG_LOG(G3D,"DL Light %i %c att: %f", l, c+'X', val); - gstate.lightatt[l][c] = val; + gstate_c.lightatt[l][c] = val; } break; @@ -589,9 +588,9 @@ void NullGPU::ExecuteOp(u32 op, u32 diff) int l = (cmd - GE_CMD_LAC0) / 3; int t = (cmd - GE_CMD_LAC0) % 3; - gstate.lightColor[t][l].r = r; - gstate.lightColor[t][l].g = g; - gstate.lightColor[t][l].b = b; + gstate_c.lightColor[t][l].r = r; + gstate_c.lightColor[t][l].g = g; + gstate_c.lightColor[t][l].b = b; } break; @@ -618,9 +617,9 @@ void NullGPU::ExecuteOp(u32 op, u32 diff) break; case GE_CMD_PATCHDIVISION: - gstate.patch_div_s = data & 0xFF; - gstate.patch_div_t = (data >> 8) & 0xFF; - DEBUG_LOG(G3D, "DL Patch subdivision: %i x %i", gstate.patch_div_s, gstate.patch_div_t); + gstate_c.patch_div_s = data & 0xFF; + gstate_c.patch_div_t = (data >> 8) & 0xFF; + DEBUG_LOG(G3D, "DL Patch subdivision: %i x %i", gstate_c.patch_div_s, gstate_c.patch_div_t); break; case GE_CMD_MATERIALUPDATE: @@ -731,7 +730,7 @@ void NullGPU::ExecuteOp(u32 op, u32 diff) int index = cmd - GE_CMD_MORPHWEIGHT0; float weight = getFloat24(data); DEBUG_LOG(G3D,"DL MorphWeight %i = %f", index, weight); - gstate.morphWeights[index] = weight; + gstate_c.morphWeights[index] = weight; } break; diff --git a/GPU/Null/NullGpu.h b/GPU/Null/NullGpu.h index 26e49c53cb..6f55182097 100644 --- a/GPU/Null/NullGpu.h +++ b/GPU/Null/NullGpu.h @@ -24,11 +24,16 @@ class ShaderManager; class NullGPU : public GPUInterface { public: + NullGPU() : interruptsEnabled_(true) {} virtual u32 EnqueueList(u32 listpc, u32 stall); virtual void UpdateStall(int listid, u32 newstall); virtual void ExecuteOp(u32 op, u32 diff); virtual bool InterpretList(); virtual void DrawSync(int mode); + virtual void EnableInterrupts(bool enable) { + interruptsEnabled_ = enable; + } private: bool ProcessDLQueue(); + bool interruptsEnabled_; }; diff --git a/SDL/buildassets.sh b/SDL/buildassets.sh index 48fd66387c..82f8ca3df6 100755 --- a/SDL/buildassets.sh +++ b/SDL/buildassets.sh @@ -1 +1,2 @@ cp -r ../android/assets . +cp ../ppge_atlas.zim assets diff --git a/Windows/PPSSPP.vcxproj b/Windows/PPSSPP.vcxproj index b5a069f277..1b399343c9 100644 --- a/Windows/PPSSPP.vcxproj +++ b/Windows/PPSSPP.vcxproj @@ -89,6 +89,10 @@ + + ..\ + PPSSPPDebug + Disabled @@ -105,7 +109,6 @@ XInput.lib;Winmm.lib;Ws2_32.lib;opengl32.lib;glu32.lib;comctl32.lib;dsound.lib;xinput.lib;%(AdditionalDependencies) true - $(OutDir)$(ProjectName).pdb Windows MachineX86 $(OutDir)$(TargetName)$(TargetExt) diff --git a/android/assets/ppge_atlas.zim b/android/assets/ppge_atlas.zim new file mode 100644 index 0000000000000000000000000000000000000000..99114d6ac2bbf069eaa4d68b84d71fb11ba90689 GIT binary patch literal 131088 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 Date: Sun, 18 Nov 2012 13:14:22 +0100 Subject: [PATCH 03/12] Get rid of opengl include from sceDisplay.cpp --- Core/HLE/sceDisplay.cpp | 18 ++---------------- GPU/GLES/DisplayListInterpreter.cpp | 7 +++++++ GPU/GLES/DisplayListInterpreter.h | 1 + GPU/GPUInterface.h | 1 + GPU/Null/NullGpu.h | 1 + 5 files changed, 12 insertions(+), 16 deletions(-) diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp index 35ccc4d707..cd13abb232 100644 --- a/Core/HLE/sceDisplay.cpp +++ b/Core/HLE/sceDisplay.cpp @@ -15,18 +15,6 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. -#if defined(ANDROID) || defined(BLACKBERRY) -#include -#include -#else -#include -#if defined(__APPLE__) -#include -#else -#include -#endif -#endif - #include //#include "base/timeutil.h" @@ -46,7 +34,7 @@ #include "../../GPU/GLES/Framebuffer.h" #include "../../GPU/GLES/ShaderManager.h" #include "../../GPU/GPUState.h" - +#include "../../GPU/GPUInterface.h" // Internal drawing library #include "../Util/PPGeDraw.h" @@ -190,9 +178,7 @@ u32 sceDisplaySetMode(u32 unknown, u32 xres, u32 yres) DEBUG_LOG(HLE,"sceDisplaySetMode(%d,%d,%d)",unknown,xres,yres); host->BeginFrame(); - glClearColor(0,0,0,1); -// glClearColor(1,0,1,1); - glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + gpu->InitClear(); return 0; } diff --git a/GPU/GLES/DisplayListInterpreter.cpp b/GPU/GLES/DisplayListInterpreter.cpp index 4e02d04609..49e8b41bae 100644 --- a/GPU/GLES/DisplayListInterpreter.cpp +++ b/GPU/GLES/DisplayListInterpreter.cpp @@ -76,6 +76,13 @@ bool finished; u8 bezierBuf[16000]; +void GLES_GPU::InitClear() +{ + glClearColor(0,0,0,1); + // glClearColor(1,0,1,1); + glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); +} + bool GLES_GPU::ProcessDLQueue() { std::vector::iterator iter = dlQueue.begin(); diff --git a/GPU/GLES/DisplayListInterpreter.h b/GPU/GLES/DisplayListInterpreter.h index 6a67b42f19..bf21e28811 100644 --- a/GPU/GLES/DisplayListInterpreter.h +++ b/GPU/GLES/DisplayListInterpreter.h @@ -25,6 +25,7 @@ class GLES_GPU : public GPUInterface { public: GLES_GPU() : interruptsEnabled_(true) {} + virtual void InitClear(); virtual u32 EnqueueList(u32 listpc, u32 stall); virtual void UpdateStall(int listid, u32 newstall); virtual void ExecuteOp(u32 op, u32 diff); diff --git a/GPU/GPUInterface.h b/GPU/GPUInterface.h index 049f720833..24476cc082 100644 --- a/GPU/GPUInterface.h +++ b/GPU/GPUInterface.h @@ -22,6 +22,7 @@ class GPUInterface { public: + virtual void InitClear() = 0; virtual u32 EnqueueList(u32 listpc, u32 stall) = 0; virtual void UpdateStall(int listid, u32 newstall) = 0; virtual void ExecuteOp(u32 op, u32 diff) = 0; diff --git a/GPU/Null/NullGpu.h b/GPU/Null/NullGpu.h index 6f55182097..bf1953a798 100644 --- a/GPU/Null/NullGpu.h +++ b/GPU/Null/NullGpu.h @@ -25,6 +25,7 @@ class NullGPU : public GPUInterface { public: NullGPU() : interruptsEnabled_(true) {} + virtual void InitClear() {} virtual u32 EnqueueList(u32 listpc, u32 stall); virtual void UpdateStall(int listid, u32 newstall); virtual void ExecuteOp(u32 op, u32 diff); From 75412b064be099643ae09b93a1e740ef455e0cc4 Mon Sep 17 00:00:00 2001 From: Henrik Rydgard Date: Sun, 18 Nov 2012 17:51:14 +0100 Subject: [PATCH 04/12] Implement sceUtilityMsgDialog --- Core/HLE/sceCtrl.cpp | 7 ++- Core/HLE/sceCtrl.h | 3 ++ Core/HLE/sceDisplay.cpp | 7 +++ Core/HLE/sceUtility.cpp | 83 ++++++++++++++++++++++++++--- Core/Util/PPGeDraw.h | 6 +-- GPU/GLES/DisplayListInterpreter.cpp | 4 +- 6 files changed, 97 insertions(+), 13 deletions(-) diff --git a/Core/HLE/sceCtrl.cpp b/Core/HLE/sceCtrl.cpp index 787f533a88..02511f7ad6 100644 --- a/Core/HLE/sceCtrl.cpp +++ b/Core/HLE/sceCtrl.cpp @@ -46,7 +46,6 @@ struct CtrlLatch { }; - ////////////////////////////////////////////////////////////////////////// // STATE BEGIN static bool ctrlInited = false; @@ -79,6 +78,12 @@ void UpdateLatch() { oldButtons = ctrl.buttons; } + +u32 __CtrlPeekButtons() +{ + return ctrl.buttons; +} + // Functions so that the rest of the emulator can control what the sceCtrl interface should return // to the game: diff --git a/Core/HLE/sceCtrl.h b/Core/HLE/sceCtrl.h index 10bb163c14..3e02e8feb3 100644 --- a/Core/HLE/sceCtrl.h +++ b/Core/HLE/sceCtrl.h @@ -36,3 +36,6 @@ void __CtrlButtonDown(u32 buttonBit); void __CtrlButtonUp(u32 buttonBit); // -1 to 1, try to keep it in the circle void __CtrlSetAnalog(float x, float y); + +// For use by internal UI like MsgDialog +u32 __CtrlPeekButtons(); \ No newline at end of file diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp index cd13abb232..e494e3e74e 100644 --- a/Core/HLE/sceDisplay.cpp +++ b/Core/HLE/sceDisplay.cpp @@ -131,6 +131,13 @@ void hleEnterVblank(u64 userdata, int cyclesLate) // Draw screen overlays before blitting. Saves and restores the Ge context. + /* + if (g_Config.bShowGPUStats) + { + char stats[512]; + sprintf(stats, "Draw calls") + }*/ + /* PPGeBegin(); PPGeDrawImage(I_LOGO, 5, 5, 0, 0xFFFFFFFF); diff --git a/Core/HLE/sceUtility.cpp b/Core/HLE/sceUtility.cpp index d3b50a28a0..63dafa1d88 100644 --- a/Core/HLE/sceUtility.cpp +++ b/Core/HLE/sceUtility.cpp @@ -290,13 +290,15 @@ struct pspMessageDialog u32 buttonPressed; // 0=?, 1=Yes, 2=No, 3=Back }; -static pspMessageDialog messageDialog; +u32 messageDialogAddr; void sceUtilityMsgDialogInitStart() { u32 structAddr = PARAM(0); DEBUG_LOG(HLE,"FAKE sceUtilityMsgDialogInitStart(%i)", structAddr); - Memory::ReadStruct(structAddr, &messageDialog); + messageDialogAddr = structAddr; + pspMessageDialog messageDialog; + Memory::ReadStruct(messageDialogAddr, &messageDialog); if (messageDialog.type == 0) // number { INFO_LOG(HLE, "MsgDialog: %08x", messageDialog.errorNum); @@ -317,11 +319,78 @@ void sceUtilityMsgDialogShutdownStart() void sceUtilityMsgDialogUpdate() { - DEBUG_LOG(HLE,"FAKE sceUtilityMsgDialogUpdate(%i)", PARAM(0)); - __UtilityUpdate(); - // PPGeBegin(); - - // PPGeEnd(); + DEBUG_LOG(HLE,"sceUtilityMsgDialogUpdate(%i)", PARAM(0)); + + switch (utilityDialogState) { + case SCE_UTILITY_STATUS_FINISHED: + utilityDialogState = SCE_UTILITY_STATUS_SHUTDOWN; + break; + } + + if (utilityDialogState != SCE_UTILITY_STATUS_RUNNING) + { + RETURN(0); + return; + } + + pspMessageDialog messageDialog; + Memory::ReadStruct(messageDialogAddr, &messageDialog); + const char *text; + if (messageDialog.type == 0) { + char temp[256]; + sprintf(temp, "Error code: %08x", messageDialog.errorNum); + text = temp; + } else { + text = messageDialog.string; + } + + PPGeBegin(); + + PPGeDraw4Patch(I_BUTTON, 0, 0, 480, 272, 0xcFFFFFFF); + PPGeDrawText(text, 50, 50, PPGE_ALIGN_LEFT, 0.5f, 0xFFFFFFFF); + + static u32 lastButtons = 0; + u32 buttons = __CtrlPeekButtons(); + + if (messageDialog.options & 0x10) //yesnobutton + { + PPGeDrawImage(I_CROSS, 80, 220, 0, 0xFFFFFFFF); + PPGeDrawText("Yes", 140, 220, PPGE_ALIGN_HCENTER, 1.0f, 0xFFFFFFFF); + PPGeDrawImage(I_CIRCLE, 200, 220, 0, 0xFFFFFFFF); + PPGeDrawText("No", 260, 220, PPGE_ALIGN_HCENTER, 1.0f, 0xFFFFFFFF); + PPGeDrawImage(I_TRIANGLE, 320, 220, 0, 0xcFFFFFFF); + PPGeDrawText("Back", 380, 220, PPGE_ALIGN_HCENTER, 1.0f, 0xcFFFFFFF); + if (!lastButtons) { + if (buttons & CTRL_TRIANGLE) { + messageDialog.buttonPressed = 3; // back + utilityDialogState = SCE_UTILITY_STATUS_FINISHED; + } else if (buttons & CTRL_CROSS) { + messageDialog.buttonPressed = 1; + utilityDialogState = SCE_UTILITY_STATUS_FINISHED; + } else if (buttons & CTRL_CIRCLE) { + messageDialog.buttonPressed = 2; + utilityDialogState = SCE_UTILITY_STATUS_FINISHED; + } + } + } + else + { + PPGeDrawImage(I_CROSS, 150, 220, 0, 0xFFFFFFFF); + PPGeDrawText("OK", 480/2, 220, PPGE_ALIGN_HCENTER, 1.0f, 0xFFFFFFFF); + if (!lastButtons) { + if (buttons & (CTRL_CROSS | CTRL_CIRCLE)) { // accept both + messageDialog.buttonPressed = 1; + utilityDialogState = SCE_UTILITY_STATUS_FINISHED; + } + } + } + + lastButtons = buttons; + + Memory::WriteStruct(messageDialogAddr, &messageDialog); + + PPGeEnd(); + RETURN(0); } diff --git a/Core/Util/PPGeDraw.h b/Core/Util/PPGeDraw.h index 70018ba867..3ad719fe0e 100644 --- a/Core/Util/PPGeDraw.h +++ b/Core/Util/PPGeDraw.h @@ -55,11 +55,11 @@ enum { // These functions must be called between PPGeBegin and PPGeEnd. // Draws some text using the one font we have. -void PPGeDrawText(const char *text, float x, float y, int align, float scale, u32 color); +void PPGeDrawText(const char *text, float x, float y, int align, float scale = 1.0f, u32 color = 0xFFFFFFFF); // Draws a "4-patch" for button-like things that can be resized -void PPGeDraw4Patch(int atlasImage, float x, float y, float w, float h, u32 color); +void PPGeDraw4Patch(int atlasImage, float x, float y, float w, float h, u32 color = 0xFFFFFFFF); // Just blits an image to the screen, multiplied with the color. -void PPGeDrawImage(int atlasImage, float x, float y, int align, u32 color); +void PPGeDrawImage(int atlasImage, float x, float y, int align, u32 color = 0xFFFFFFFF); diff --git a/GPU/GLES/DisplayListInterpreter.cpp b/GPU/GLES/DisplayListInterpreter.cpp index 49e8b41bae..0bb6977e43 100644 --- a/GPU/GLES/DisplayListInterpreter.cpp +++ b/GPU/GLES/DisplayListInterpreter.cpp @@ -113,8 +113,8 @@ u32 GLES_GPU::EnqueueList(u32 listpc, u32 stall) { DisplayList dl; dl.id = dlIdGenerator++; - dl.listpc = listpc&0xFFFFFFF; - dl.stall = stall&0xFFFFFFF; + dl.listpc = listpc & 0xFFFFFFF; + dl.stall = stall & 0xFFFFFFF; dlQueue.push_back(dl); if (!ProcessDLQueue()) return dl.id; From 9ec858a64d18939da8b87ce10bdce1861f8f4124 Mon Sep 17 00:00:00 2001 From: Henrik Rydgard Date: Sun, 18 Nov 2012 18:47:29 +0100 Subject: [PATCH 05/12] Bugfix an 8-bit indexed texture format --- GPU/GLES/TextureCache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPU/GLES/TextureCache.cpp b/GPU/GLES/TextureCache.cpp index 90e4fa3cc7..680d004427 100644 --- a/GPU/GLES/TextureCache.cpp +++ b/GPU/GLES/TextureCache.cpp @@ -238,7 +238,7 @@ void *readIndexedTex(u32 level, u32 texaddr, u32 bytesPerIndex) u32 n = tmpTexBuf32[j]; u32 k; for (k = 0; k < 4; k++) { - u8 index = (n >> (k * 4)) & 0xff; + u8 index = (n >> (k * 8)) & 0xff; tmpTexBuf16[i + k] = clut[GetClutIndex(index)]; } } From 71a3b789b8d82c8053060d5f2a9a1c19d5face92 Mon Sep 17 00:00:00 2001 From: "kev :)" Date: Sun, 18 Nov 2012 19:33:19 +0000 Subject: [PATCH 06/12] Add fake methods that use sceGetCurrentTick --- Core/HLE/HLETables.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/HLE/HLETables.cpp b/Core/HLE/HLETables.cpp index d434d337ec..ae0f4d5bb8 100644 --- a/Core/HLE/HLETables.cpp +++ b/Core/HLE/HLETables.cpp @@ -98,8 +98,8 @@ const HLEFunction sceRtc[] = { {0xC41C2853, sceRtcGetTickResolution, "sceRtcGetTickResolution"}, {0x3f7ad767, sceRtcGetCurrentTick, "sceRtcGetCurrentTick"}, - {0x011F03C1, 0, "sceRtcGetAccumulativeTime"}, - {0x029CA3B3, 0, "sceRtcGetAccumlativeTime"}, + {0x011F03C1, sceRtcGetCurrentTick, "sceRtcGetAccumulativeTime"}, + {0x029CA3B3, sceRtcGetCurrentTick, "sceRtcGetAccumlativeTime"}, {0x4cfa57b0, 0, "sceRtcGetCurrentClock"}, {0xE7C27D1B, sceRtcGetCurrentClockLocalTime, "sceRtcGetCurrentClockLocalTime"}, {0x34885E0D, 0, "sceRtcConvertUtcToLocalTime"}, From 48bd2ccc083af4e4970904cd0300ba881cbea892 Mon Sep 17 00:00:00 2001 From: "kev :)" Date: Sun, 18 Nov 2012 19:34:10 +0000 Subject: [PATCH 07/12] Attempt at UMD callbacks. Helps a few games but needs more work. --- Core/HLE/sceUmd.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Core/HLE/sceUmd.cpp b/Core/HLE/sceUmd.cpp index f520f78e8b..2eb6a27c27 100644 --- a/Core/HLE/sceUmd.cpp +++ b/Core/HLE/sceUmd.cpp @@ -31,6 +31,7 @@ u8 umdActivated = 1; u32 umdStatus = 0; u32 umdErrorStat = 0; +static u32 driveCBId; #define PSP_UMD_TYPE_GAME 0x10 @@ -120,6 +121,7 @@ u32 sceUmdDeactivate(u32 unknown, const char *name) u32 sceUmdRegisterUMDCallBack(u32 cbId) { DEBUG_LOG(HLE,"0=sceUmdRegisterUMDCallback(id=%i)",PARAM(0)); + driveCBId = cbId; return __KernelRegisterCallback(THREAD_CALLBACK_UMD, cbId); } @@ -148,24 +150,30 @@ void sceUmdWaitDriveStat() { u32 stat = PARAM(0); ERROR_LOG(HLE,"UNIMPL 0=sceUmdWaitDriveStat(stat = %08x)", stat); - //if ((stat & __KernelUmdGetState()) != stat) - // __KernelWaitCurThread(WAITTYPE_UMD, 0, stat, 0, 0); //__KernelWaitCurThread(WAITTYPE_UMD, 0); + if ((stat & __KernelUmdGetState()) != stat) + __KernelWaitCurThread(WAITTYPE_UMD, 0, stat, 0, 0); //__KernelWaitCurThread(WAITTYPE_UMD, 0); RETURN(0); } void sceUmdWaitDriveStatWithTimer() { u32 stat = PARAM(0); + u32 timeout = PARAM(1); ERROR_LOG(HLE,"UNIMPL 0=sceUmdWaitDriveStatWithTimer(stat = %08x)", stat); - //__KernelWaitCurThread(WAITTYPE_UMD, 0); - RETURN(0); + if ((stat & __KernelUmdGetState()) != stat) + __KernelWaitCurThread(WAITTYPE_UMD, 0, stat, 0, 0); //__KernelWaitCurThread(WAITTYPE_UMD, 0); + RETURN(stat); } + void sceUmdWaitDriveStatCB() { u32 stat = PARAM(0); ERROR_LOG(HLE,"UNIMPL 0=sceUmdWaitDriveStatCB(stat = %08x)", stat); - //__KernelWaitCurThread(WAITTYPE_UMD, 0); + //__KernelRegisterCallback(THREAD_CALLBACK_UMD, cbid); + // Immediately notify + RETURN(0); + __KernelNotifyCallbackType(THREAD_CALLBACK_UMD, driveCBId, __KernelUmdGetState()&stat); RETURN(0); } From 9257b28c15ef3b4c5819422146fa51182a993465 Mon Sep 17 00:00:00 2001 From: "kev :)" Date: Sun, 18 Nov 2012 19:34:53 +0000 Subject: [PATCH 08/12] Limit thread name Seems this needs to be limited --- Core/HLE/sceKernelThread.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp index e2b8105325..1e1bcf719f 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -1288,8 +1288,8 @@ u32 __KernelCreateCallback(const char *name, u32 entrypoint, u32 commonArg) SceUID id = kernelObjects.Create(cb); cb->nc.size = sizeof(NativeCallback); - strcpy(cb->nc.name, name); - + strncpy(cb->nc.name, name, 32); + cb->nc.entrypoint = entrypoint; cb->nc.threadId = __KernelGetCurThread(); cb->nc.commonArgument = commonArg; From 03bc871f56369f4259a5f92e0d47eeb0d15c99fd Mon Sep 17 00:00:00 2001 From: "kev :)" Date: Sun, 18 Nov 2012 20:13:27 +0000 Subject: [PATCH 09/12] sceDisplayWaitVblankStartMultiCB() This is wrong but better than having nothing in the method. The one game I have found that uses this seems to be ok with this implementation. --- Core/HLE/sceDisplay.cpp | 9 ++++++++- Core/HLE/sceUmd.cpp | 9 ++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp index e494e3e74e..7056903cc7 100644 --- a/Core/HLE/sceDisplay.cpp +++ b/Core/HLE/sceDisplay.cpp @@ -259,6 +259,13 @@ void sceDisplayWaitVblankStartCB() __KernelCheckCallbacks(); } +void sceDisplayWaitVblankStartMultiCB() +{ + DEBUG_LOG(HLE,"sceDisplayWaitVblankStartMultiCB()"); + __KernelWaitCurThread(WAITTYPE_VBLANK, 0, 0, 0, true); + __KernelCheckCallbacks(); +} + void sceDisplayGetVcount() { // Too spammy @@ -297,7 +304,7 @@ const HLEFunction sceDisplay[] = {0x984C27E7,sceDisplayWaitVblankStart, "sceDisplayWaitVblankStart"}, {0x8EB9EC49,sceDisplayWaitVblankCB, "sceDisplayWaitVblankCB"}, {0x46F186C3,sceDisplayWaitVblankStartCB, "sceDisplayWaitVblankStartCB"}, - {0x77ed8b3a,0,"sceDisplayWaitVblankStartMultiCB"}, + {0x77ed8b3a,sceDisplayWaitVblankStartMultiCB,"sceDisplayWaitVblankStartMultiCB"}, {0xdba6c4c4,&WrapF_V,"sceDisplayGetFramePerSec"}, {0x773dd3a3,sceDisplayGetCurrentHcount,"sceDisplayGetCurrentHcount"}, diff --git a/Core/HLE/sceUmd.cpp b/Core/HLE/sceUmd.cpp index 2eb6a27c27..c7b521e399 100644 --- a/Core/HLE/sceUmd.cpp +++ b/Core/HLE/sceUmd.cpp @@ -149,7 +149,7 @@ u32 sceUmdGetDriveStat() void sceUmdWaitDriveStat() { u32 stat = PARAM(0); - ERROR_LOG(HLE,"UNIMPL 0=sceUmdWaitDriveStat(stat = %08x)", stat); + DEBUG_LOG(HLE,"HACK 0=sceUmdWaitDriveStat(stat = %08x)", stat); if ((stat & __KernelUmdGetState()) != stat) __KernelWaitCurThread(WAITTYPE_UMD, 0, stat, 0, 0); //__KernelWaitCurThread(WAITTYPE_UMD, 0); RETURN(0); @@ -159,18 +159,17 @@ void sceUmdWaitDriveStatWithTimer() { u32 stat = PARAM(0); u32 timeout = PARAM(1); - ERROR_LOG(HLE,"UNIMPL 0=sceUmdWaitDriveStatWithTimer(stat = %08x)", stat); + DEBUG_LOG(HLE,"HACK 0=sceUmdWaitDriveStatWithTimer(stat = %08x)", stat); if ((stat & __KernelUmdGetState()) != stat) __KernelWaitCurThread(WAITTYPE_UMD, 0, stat, 0, 0); //__KernelWaitCurThread(WAITTYPE_UMD, 0); - RETURN(stat); + RETURN(0); } void sceUmdWaitDriveStatCB() { u32 stat = PARAM(0); - ERROR_LOG(HLE,"UNIMPL 0=sceUmdWaitDriveStatCB(stat = %08x)", stat); - //__KernelRegisterCallback(THREAD_CALLBACK_UMD, cbid); + DEBUG_LOG(HLE,"HACK 0=sceUmdWaitDriveStatCB(stat = %08x)", stat); // Immediately notify RETURN(0); __KernelNotifyCallbackType(THREAD_CALLBACK_UMD, driveCBId, __KernelUmdGetState()&stat); From 7c3064660589f42ccd0488792e46ed01dc4af197 Mon Sep 17 00:00:00 2001 From: "kev :)" Date: Sun, 18 Nov 2012 21:07:40 +0000 Subject: [PATCH 10/12] Don't append / when there is already one Was causing marvel allience 2 to crash --- Core/FileSystems/MetaFileSystem.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Core/FileSystems/MetaFileSystem.cpp b/Core/FileSystems/MetaFileSystem.cpp index 3427ceef4b..7ca1a9a619 100644 --- a/Core/FileSystems/MetaFileSystem.cpp +++ b/Core/FileSystems/MetaFileSystem.cpp @@ -116,12 +116,25 @@ PSPFileInfo MetaFileSystem::GetFileInfo(std::string filename) } } +//TODO: Not sure where this should live. Seems a bit wrong putting it in common +bool stringEndsWith (std::string const &fullString, std::string const &ending) +{ + if (fullString.length() >= ending.length()) { + return (0 == fullString.compare (fullString.length() - ending.length(), ending.length(), ending)); + } else { + return false; + } +} + std::vector MetaFileSystem::GetDirListing(std::string path) { std::string of; if (path.find(':') == std::string::npos) { - path = currentDirectory + "/" + path; + if (!stringEndsWith(currentDirectory, "/")) + { + path = currentDirectory + "/" + path; + } DEBUG_LOG(HLE,"GetFileInfo: Expanded path to %s", path.c_str()); } IFileSystem *system; From d04fb7cb50a597bb081a7dff02edb0ef8bf6d522 Mon Sep 17 00:00:00 2001 From: "kev :)" Date: Sun, 18 Nov 2012 21:28:26 +0000 Subject: [PATCH 11/12] A bit more clean up on umd callbacks Just error checking based on some tests --- Core/HLE/sceUmd.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/Core/HLE/sceUmd.cpp b/Core/HLE/sceUmd.cpp index c7b521e399..81929ca7e6 100644 --- a/Core/HLE/sceUmd.cpp +++ b/Core/HLE/sceUmd.cpp @@ -31,7 +31,7 @@ u8 umdActivated = 1; u32 umdStatus = 0; u32 umdErrorStat = 0; -static u32 driveCBId; +static int driveCBId= -1; #define PSP_UMD_TYPE_GAME 0x10 @@ -47,6 +47,7 @@ void __UmdInit() { umdActivated = 1; umdStatus = 0; umdErrorStat = 0; + driveCBId = -1; } u8 __KernelUmdGetState() @@ -121,13 +122,21 @@ u32 sceUmdDeactivate(u32 unknown, const char *name) u32 sceUmdRegisterUMDCallBack(u32 cbId) { DEBUG_LOG(HLE,"0=sceUmdRegisterUMDCallback(id=%i)",PARAM(0)); - driveCBId = cbId; + if (driveCBId == -1) + { + driveCBId = cbId; + } + else + { + ERROR_LOG(HLE," 0=sceUmdRegisterUMDCallback(id=%i) callback overwrite attempt",PARAM(0)); + } return __KernelRegisterCallback(THREAD_CALLBACK_UMD, cbId); } u32 sceUmdUnRegisterUMDCallBack(u32 cbId) { DEBUG_LOG(HLE,"0=sceUmdUnRegisterUMDCallBack(id=%i)",PARAM(0)); + driveCBId = -1; return __KernelUnregisterCallback(THREAD_CALLBACK_UMD, cbId); } @@ -172,7 +181,14 @@ void sceUmdWaitDriveStatCB() DEBUG_LOG(HLE,"HACK 0=sceUmdWaitDriveStatCB(stat = %08x)", stat); // Immediately notify RETURN(0); - __KernelNotifyCallbackType(THREAD_CALLBACK_UMD, driveCBId, __KernelUmdGetState()&stat); + if (driveCBId != -1) + { + __KernelNotifyCallbackType(THREAD_CALLBACK_UMD, driveCBId, __KernelUmdGetState()&stat); + } + else + { + ERROR_LOG(HLE, "HACK 0=sceUmdWaitDriveStatCB(stat = %08x) attempting to call unset callback", stat); + } RETURN(0); } From bcb0cbe7eae444d7516e823bc8040e3527ef32dc Mon Sep 17 00:00:00 2001 From: "kev :)" Date: Sun, 18 Nov 2012 21:45:14 +0000 Subject: [PATCH 12/12] make sceUtilitySavedataInitStart return 0 Makes a few other games get further --- Core/HLE/sceUtility.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Core/HLE/sceUtility.cpp b/Core/HLE/sceUtility.cpp index 63dafa1d88..e67f4f61d1 100644 --- a/Core/HLE/sceUtility.cpp +++ b/Core/HLE/sceUtility.cpp @@ -198,7 +198,7 @@ void sceUtilitySavedataInitStart() { SceUtilitySavedataParam *param = (SceUtilitySavedataParam*)Memory::GetPointer(PARAM(0)); - DEBUG_LOG(HLE,"sceUtilitySavedataInitStart(%08x)", PARAM(0)); + DEBUG_LOG(HLE,"sceUtilitySavedataInitStart(%08x)", PARAM(0)); DEBUG_LOG(HLE,"Mode: %i", param->mode); if (param->mode == 0) //load { @@ -214,9 +214,11 @@ void sceUtilitySavedataInitStart() __UtilityInitStart(); + // Returning 0 here breaks Bust a Move Deluxe! But should be the right thing to do... + // At least Cohort Chess expects this to return 0 or it locks up.. // The fix is probably to fully implement sceUtility so that it actually works. - // RETURN(0); + RETURN(0); } void sceUtilitySavedataShutdownStart()