Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/HLE/sceDisplay.cpp
5676 views
1
// Copyright (c) 2012- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include <algorithm>
19
#include <cmath>
20
#include <map>
21
#include <mutex>
22
#include <vector>
23
24
// TODO: Move this somewhere else, cleanup.
25
#ifndef _WIN32
26
#include <unistd.h>
27
#include <sys/time.h>
28
#endif
29
30
#include "Common/Data/Text/I18n.h"
31
#include "Common/Profiler/Profiler.h"
32
#include "Common/System/System.h"
33
#include "Common/System/OSD.h"
34
#include "Common/Serialize/Serializer.h"
35
#include "Common/Serialize/SerializeFuncs.h"
36
#include "Common/Serialize/SerializeMap.h"
37
#include "Common/TimeUtil.h"
38
#include "Core/Config.h"
39
#include "Core/CoreTiming.h"
40
#include "Core/CoreParameter.h"
41
#include "Core/FrameTiming.h"
42
#include "Core/Reporting.h"
43
#include "Core/Core.h"
44
#include "Core/System.h"
45
#include "Core/HLE/HLE.h"
46
#include "Core/HLE/ErrorCodes.h"
47
#include "Core/HLE/FunctionWrappers.h"
48
#include "Core/HLE/sceDisplay.h"
49
#include "Core/HLE/sceKernel.h"
50
#include "Core/HLE/sceNet.h"
51
#include "Core/HLE/sceKernelThread.h"
52
#include "Core/HLE/sceKernelInterrupt.h"
53
#include "Core/HW/Display.h"
54
#include "Core/Util/PPGeDraw.h"
55
#include "Core/RetroAchievements.h"
56
57
#include "GPU/GPU.h"
58
#include "GPU/GPUState.h"
59
#include "GPU/GPUCommon.h"
60
#include "GPU/Common/FramebufferManagerCommon.h"
61
#include "GPU/Common/PostShader.h"
62
#include "GPU/Debugger/Record.h"
63
64
struct FrameBufferState {
65
u32 topaddr;
66
GEBufferFormat fmt;
67
int stride;
68
};
69
70
struct WaitVBlankInfo {
71
WaitVBlankInfo(u32 tid) : threadID(tid), vcountUnblock(1) {}
72
WaitVBlankInfo(u32 tid, int vcount) : threadID(tid), vcountUnblock(vcount) {}
73
SceUID threadID;
74
// Number of vcounts to block for.
75
int vcountUnblock;
76
77
void DoState(PointerWrap &p) {
78
auto s = p.Section("WaitVBlankInfo", 1);
79
if (!s)
80
return;
81
82
Do(p, threadID);
83
Do(p, vcountUnblock);
84
}
85
};
86
87
// STATE BEGIN
88
static FrameBufferState framebuf;
89
static FrameBufferState latchedFramebuf;
90
static bool framebufIsLatched;
91
92
static int enterVblankEvent = -1;
93
static int leaveVblankEvent = -1;
94
static int afterFlipEvent = -1;
95
static int lagSyncEvent = -1;
96
97
static double lastLagSync = 0.0;
98
static bool lagSyncScheduled = false;
99
100
static int numSkippedFrames;
101
static bool hasSetMode;
102
static int resumeMode;
103
static int holdMode;
104
static int brightnessLevel;
105
static int mode;
106
static int width;
107
static int height;
108
static bool wasPaused;
109
static bool flippedThisFrame;
110
111
static int framerate;
112
113
// 1.001f to compensate for the classic 59.94 NTSC framerate that the PSP seems to have.
114
static double timePerVblank;
115
116
// Don't include this in the state, time increases regardless of state.
117
static double curFrameTime;
118
static double lastFrameTime;
119
static double nextFrameTime;
120
static int numVBlanksSinceFlip;
121
122
const int PSP_DISPLAY_MODE_LCD = 0;
123
124
std::vector<WaitVBlankInfo> vblankWaitingThreads;
125
// Key is the callback id it was for, or if no callback, the thread id.
126
// Value is the goal vcount number (in case the callback takes >= 1 vcount to return.)
127
std::map<SceUID, int> vblankPausedWaits;
128
129
// STATE END
130
131
// The vblank period is 731.5 us (0.7315 ms)
132
const double vblankMs = 0.7315;
133
// These are guesses based on tests.
134
const double vsyncStartMs = 0.5925;
135
const double vsyncEndMs = 0.7265;
136
double frameMs;
137
138
enum {
139
PSP_DISPLAY_SETBUF_IMMEDIATE = 0,
140
PSP_DISPLAY_SETBUF_NEXTFRAME = 1
141
};
142
143
// For the "max 60 fps" setting.
144
static int lastFlipsTooFrequent = 0;
145
static u64 lastFlipCycles = 0;
146
static u64 nextFlipCycles = 0;
147
148
void hleEnterVblank(u64 userdata, int cyclesLate);
149
void hleLeaveVblank(u64 userdata, int cyclesLate);
150
void hleAfterFlip(u64 userdata, int cyclesLate);
151
void hleLagSync(u64 userdata, int cyclesLate);
152
153
void __DisplayVblankBeginCallback(SceUID threadID, SceUID prevCallbackId);
154
void __DisplayVblankEndCallback(SceUID threadID, SceUID prevCallbackId);
155
156
void __DisplayFlip(int cyclesLate);
157
static void __DisplaySetFramerate(void);
158
159
static bool UseAutoFrameSkip() {
160
return g_Config.bAutoFrameSkip && !g_Config.bSkipBufferEffects;
161
}
162
163
static bool UseLagSync() {
164
return g_Config.bForceLagSync && !UseAutoFrameSkip();
165
}
166
167
static void ScheduleLagSync(int over = 0) {
168
lagSyncScheduled = UseLagSync();
169
if (lagSyncScheduled) {
170
// Reset over if it became too high, such as after pausing or initial loading.
171
// There's no real sense in it being more than 1/60th of a second.
172
if (over > 1000000 / framerate) {
173
over = 0;
174
}
175
CoreTiming::ScheduleEvent(usToCycles(1000 + over), lagSyncEvent, 0);
176
lastLagSync = time_now_d();
177
}
178
}
179
180
void __DisplayInit() {
181
__DisplaySetFramerate();
182
DisplayHWReset();
183
hasSetMode = false;
184
mode = 0;
185
resumeMode = 0;
186
holdMode = 0;
187
brightnessLevel = 84;
188
width = 480;
189
height = 272;
190
numSkippedFrames = 0;
191
numVBlanksSinceFlip = 0;
192
flippedThisFrame = false;
193
framebufIsLatched = false;
194
framebuf.topaddr = 0x04000000;
195
framebuf.fmt = GE_FORMAT_8888;
196
framebuf.stride = 512;
197
memcpy(&latchedFramebuf, &framebuf, sizeof(latchedFramebuf));
198
lastFlipsTooFrequent = 0;
199
lastFlipCycles = 0;
200
nextFlipCycles = 0;
201
wasPaused = false;
202
203
enterVblankEvent = CoreTiming::RegisterEvent("EnterVBlank", &hleEnterVblank);
204
leaveVblankEvent = CoreTiming::RegisterEvent("LeaveVBlank", &hleLeaveVblank);
205
afterFlipEvent = CoreTiming::RegisterEvent("AfterFlip", &hleAfterFlip);
206
207
lagSyncEvent = CoreTiming::RegisterEvent("LagSync", &hleLagSync);
208
ScheduleLagSync();
209
210
CoreTiming::ScheduleEvent(msToCycles(frameMs - vblankMs), enterVblankEvent, 0);
211
curFrameTime = 0.0;
212
nextFrameTime = 0.0;
213
lastFrameTime = 0.0;
214
215
__KernelRegisterWaitTypeFuncs(WAITTYPE_VBLANK, __DisplayVblankBeginCallback, __DisplayVblankEndCallback);
216
}
217
218
struct GPUStatistics_v0 {
219
int firstInts[11];
220
double msProcessingDisplayLists;
221
int moreInts[15];
222
};
223
224
void __DisplayDoState(PointerWrap &p) {
225
auto s = p.Section("sceDisplay", 1, 7);
226
if (!s)
227
return;
228
229
Do(p, framebuf);
230
Do(p, latchedFramebuf);
231
Do(p, framebufIsLatched);
232
DisplayHWDoState(p, s <= 2);
233
Do(p, hasSetMode);
234
Do(p, mode);
235
Do(p, resumeMode);
236
Do(p, holdMode);
237
if (s >= 4) {
238
Do(p, brightnessLevel);
239
}
240
Do(p, width);
241
Do(p, height);
242
WaitVBlankInfo wvi(0);
243
Do(p, vblankWaitingThreads, wvi);
244
Do(p, vblankPausedWaits);
245
246
Do(p, enterVblankEvent);
247
CoreTiming::RestoreRegisterEvent(enterVblankEvent, "EnterVBlank", &hleEnterVblank);
248
Do(p, leaveVblankEvent);
249
CoreTiming::RestoreRegisterEvent(leaveVblankEvent, "LeaveVBlank", &hleLeaveVblank);
250
Do(p, afterFlipEvent);
251
CoreTiming::RestoreRegisterEvent(afterFlipEvent, "AfterFlip", &hleAfterFlip);
252
253
if (s >= 5) {
254
Do(p, lagSyncEvent);
255
Do(p, lagSyncScheduled);
256
CoreTiming::RestoreRegisterEvent(lagSyncEvent, "LagSync", &hleLagSync);
257
lastLagSync = time_now_d();
258
if (lagSyncScheduled != UseLagSync()) {
259
ScheduleLagSync();
260
}
261
} else {
262
lagSyncEvent = -1;
263
CoreTiming::RestoreRegisterEvent(lagSyncEvent, "LagSync", &hleLagSync);
264
ScheduleLagSync();
265
}
266
267
Do(p, gstate);
268
269
// TODO: GPU stuff is really not the responsibility of sceDisplay.
270
// Display just displays the buffers the GPU has drawn, they are really completely distinct.
271
// Maybe a bit tricky to move at this point, though...
272
273
gstate_c.DoState(p);
274
if (s < 2) {
275
// This shouldn't have been savestated anyway, but it was.
276
// It's unlikely to overlap with the first value in gpuStats.
277
int gpuVendorTemp = 0;
278
p.ExpectVoid(&gpuVendorTemp, sizeof(gpuVendorTemp));
279
}
280
if (s < 6) {
281
GPUStatistics_v0 oldStats;
282
Do(p, oldStats);
283
}
284
285
if (s < 7) {
286
u64 now = CoreTiming::GetTicks();
287
lastFlipCycles = now;
288
nextFlipCycles = now;
289
} else {
290
Do(p, lastFlipCycles);
291
Do(p, nextFlipCycles);
292
}
293
294
gpu->DoState(p);
295
296
if (p.mode == p.MODE_READ) {
297
gpu->ReapplyGfxState();
298
gpu->SetDisplayFramebuffer(framebuf.topaddr, framebuf.stride, framebuf.fmt);
299
}
300
}
301
302
void __DisplayShutdown() {
303
vblankWaitingThreads.clear();
304
}
305
306
void __DisplayVblankBeginCallback(SceUID threadID, SceUID prevCallbackId) {
307
SceUID pauseKey = prevCallbackId == 0 ? threadID : prevCallbackId;
308
309
// This means two callbacks in a row. PSP crashes if the same callback waits inside itself (may need more testing.)
310
// TODO: Handle this better?
311
if (vblankPausedWaits.find(pauseKey) != vblankPausedWaits.end()) {
312
return;
313
}
314
315
WaitVBlankInfo waitData(0);
316
for (size_t i = 0; i < vblankWaitingThreads.size(); i++) {
317
WaitVBlankInfo *t = &vblankWaitingThreads[i];
318
if (t->threadID == threadID) {
319
waitData = *t;
320
vblankWaitingThreads.erase(vblankWaitingThreads.begin() + i);
321
break;
322
}
323
}
324
325
if (waitData.threadID != threadID) {
326
WARN_LOG_REPORT(Log::sceDisplay, "sceDisplayWaitVblankCB: could not find waiting thread info.");
327
return;
328
}
329
330
vblankPausedWaits[pauseKey] = __DisplayGetVCount() + waitData.vcountUnblock;
331
DEBUG_LOG(Log::sceDisplay, "sceDisplayWaitVblankCB: Suspending vblank wait for callback");
332
}
333
334
void __DisplayVblankEndCallback(SceUID threadID, SceUID prevCallbackId) {
335
SceUID pauseKey = prevCallbackId == 0 ? threadID : prevCallbackId;
336
337
// Probably should not be possible.
338
if (vblankPausedWaits.find(pauseKey) == vblankPausedWaits.end()) {
339
__KernelResumeThreadFromWait(threadID, 0);
340
return;
341
}
342
343
int vcountUnblock = vblankPausedWaits[pauseKey];
344
vblankPausedWaits.erase(pauseKey);
345
if (vcountUnblock <= __DisplayGetVCount()) {
346
__KernelResumeThreadFromWait(threadID, 0);
347
return;
348
}
349
350
// Still have to wait a bit longer.
351
vblankWaitingThreads.push_back(WaitVBlankInfo(__KernelGetCurThread(), vcountUnblock - __DisplayGetVCount()));
352
DEBUG_LOG(Log::sceDisplay, "sceDisplayWaitVblankCB: Resuming vblank wait from callback");
353
}
354
355
void __DisplaySetWasPaused() {
356
wasPaused = true;
357
}
358
359
// TOOD: Should return 59.997?
360
static int FrameTimingLimit() {
361
if (!NetworkAllowSpeedControl()) {
362
return 60;
363
}
364
365
bool challenge = Achievements::HardcoreModeActive();
366
367
auto fixRate = [=](int limit) {
368
int minRate = challenge ? 60 : 1;
369
if (limit != 0) {
370
return std::max(limit, minRate);
371
} else {
372
return limit;
373
}
374
};
375
376
// Note: Fast-forward is OK in hardcore mode.
377
if (PSP_CoreParameter().fastForward)
378
return 0;
379
// Can't slow down in hardcore mode.
380
if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1)
381
return fixRate(g_Config.iFpsLimit1);
382
if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM2)
383
return fixRate(g_Config.iFpsLimit2);
384
if (PSP_CoreParameter().fpsLimit == FPSLimit::ANALOG)
385
return fixRate(PSP_CoreParameter().analogFpsLimit);
386
return framerate;
387
}
388
389
static bool FrameTimingThrottled() {
390
return FrameTimingLimit() != 0;
391
}
392
393
static void DoFrameDropLogging(float scaledTimestep) {
394
if (lastFrameTime != 0.0 && !wasPaused && lastFrameTime + scaledTimestep < curFrameTime) {
395
const double actualTimestep = curFrameTime - lastFrameTime;
396
397
char stats[4096];
398
__DisplayGetDebugStats(stats, sizeof(stats));
399
NOTICE_LOG(Log::sceDisplay, "Dropping frames - budget = %.2fms / %.1ffps, actual = %.2fms (+%.2fms) / %.1ffps\n%s", scaledTimestep * 1000.0, 1.0 / scaledTimestep, actualTimestep * 1000.0, (actualTimestep - scaledTimestep) * 1000.0, 1.0 / actualTimestep, stats);
400
}
401
}
402
403
// All the throttling and frameskipping logic is here.
404
// This is called just before we drop out of the main loop, in order to allow the submit and present to happen.
405
static void DoFrameTiming(bool throttle, bool *skipFrame, float scaledTimestep, bool endOfFrame) {
406
PROFILE_THIS_SCOPE("timing");
407
*skipFrame = false;
408
409
const bool autoFrameSkip = UseAutoFrameSkip();
410
411
// Check if the frameskipping code should be enabled. If neither throttling or frameskipping is on,
412
// we have nothing to do here.
413
bool doFrameSkip = g_Config.iFrameSkip != 0;
414
if (!throttle && !doFrameSkip)
415
return;
416
417
if (lastFrameTime == 0.0 || wasPaused) {
418
nextFrameTime = time_now_d() + scaledTimestep;
419
} else {
420
// Advance lastFrameTime by a constant amount each frame,
421
// but don't let it get too far behind as things can get very jumpy.
422
const double maxFallBehindFrames = 5.5;
423
424
nextFrameTime = std::max(lastFrameTime + scaledTimestep, time_now_d() - maxFallBehindFrames * scaledTimestep);
425
}
426
curFrameTime = time_now_d();
427
428
if (g_Config.bLogFrameDrops) {
429
DoFrameDropLogging(scaledTimestep);
430
}
431
432
// Auto-frameskip automatically if speed limit is set differently than the default.
433
const int frameSkipNum = g_Config.iFrameSkip;
434
if (autoFrameSkip) {
435
// autoframeskip
436
// Argh, we are falling behind! Let's skip a frame and see if we catch up.
437
if (curFrameTime > nextFrameTime && doFrameSkip) {
438
*skipFrame = true;
439
}
440
} else if (frameSkipNum >= 1) {
441
// fixed frameskip
442
if (numSkippedFrames >= frameSkipNum)
443
*skipFrame = false;
444
else
445
*skipFrame = true;
446
}
447
448
if (curFrameTime < nextFrameTime && throttle) {
449
// If time gap is huge just jump (somebody fast-forwarded)
450
if (nextFrameTime - curFrameTime > 2*scaledTimestep) {
451
nextFrameTime = curFrameTime;
452
} else {
453
// Wait until we've caught up.
454
// If we're ending the frame here, we'll defer the sleep until after the command buffers
455
// have been handed off to the render thread, for some more overlap.
456
if (endOfFrame) {
457
g_frameTiming.DeferWaitUntil(nextFrameTime, &curFrameTime);
458
} else {
459
WaitUntil(curFrameTime, nextFrameTime, "display-wait");
460
curFrameTime = time_now_d(); // I guess we could also just set it to nextFrameTime...
461
}
462
}
463
}
464
465
lastFrameTime = nextFrameTime;
466
wasPaused = false;
467
}
468
469
static void DoFrameIdleTiming() {
470
PROFILE_THIS_SCOPE("timing");
471
if (!FrameTimingThrottled() || !g_Config.bEnableSound || wasPaused) {
472
return;
473
}
474
475
double before = time_now_d();
476
double dist = before - lastFrameTime;
477
// Ignore if the distance is just crazy. May mean wrap or pause.
478
if (dist < 0.0 || dist >= 15.0 * timePerVblank) {
479
return;
480
}
481
482
float scaledVblank = timePerVblank;
483
int fpsLimit = FrameTimingLimit();
484
if (fpsLimit != 0 && fpsLimit != framerate) {
485
// 0 is handled in FrameTimingThrottled().
486
scaledVblank *= (float)framerate / fpsLimit;
487
}
488
489
// If we have over at least a vblank of spare time, maintain at least 30fps in delay.
490
// This prevents fast forward during loading screens.
491
// Give a little extra wiggle room in case the next vblank does more work.
492
const double goal = lastFrameTime + (numVBlanksSinceFlip - 1) * scaledVblank - 0.001;
493
if (numVBlanksSinceFlip >= 2 && before < goal) {
494
double cur_time;
495
while ((cur_time = time_now_d()) < goal) {
496
#ifdef _WIN32
497
sleep_ms(1, "frame-idle");
498
#else
499
const double left = goal - cur_time;
500
if (left > 0.0f && left < 1.0f) { // Sanity check
501
usleep((long)(left * 1000000));
502
}
503
#endif
504
}
505
506
if ((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::FRAME_GRAPH || coreCollectDebugStats) {
507
DisplayNotifySleep(time_now_d() - before);
508
}
509
}
510
}
511
512
void hleEnterVblank(u64 userdata, int cyclesLate) {
513
int vbCount = userdata;
514
515
VERBOSE_LOG(Log::sceDisplay, "Enter VBlank %i", vbCount);
516
517
DisplayFireVblankStart();
518
519
CoreTiming::ScheduleEvent(msToCycles(vblankMs) - cyclesLate, leaveVblankEvent, vbCount + 1);
520
521
// Trigger VBlank interrupt handlers.
522
__TriggerInterrupt(PSP_INTR_IMMEDIATE | PSP_INTR_ONLY_IF_ENABLED | PSP_INTR_ALWAYS_RESCHED, PSP_VBLANK_INTR, PSP_INTR_SUB_ALL);
523
524
// Wake up threads waiting for VBlank
525
u32 error;
526
bool wokeThreads = false;
527
for (size_t i = 0; i < vblankWaitingThreads.size(); i++) {
528
if (--vblankWaitingThreads[i].vcountUnblock == 0) {
529
// Only wake it if it wasn't already released by someone else.
530
SceUID waitID = __KernelGetWaitID(vblankWaitingThreads[i].threadID, WAITTYPE_VBLANK, error);
531
if (waitID == 1) {
532
__KernelResumeThreadFromWait(vblankWaitingThreads[i].threadID, 0);
533
wokeThreads = true;
534
}
535
vblankWaitingThreads.erase(vblankWaitingThreads.begin() + i--);
536
}
537
}
538
if (wokeThreads) {
539
__KernelReSchedule("entered vblank");
540
}
541
542
numVBlanksSinceFlip++;
543
544
// TODO: Should this be done here or in hleLeaveVblank?
545
if (framebufIsLatched) {
546
DEBUG_LOG(Log::sceDisplay, "Setting latched framebuffer %08x (prev: %08x)", latchedFramebuf.topaddr, framebuf.topaddr);
547
framebuf = latchedFramebuf;
548
framebufIsLatched = false;
549
gpu->SetDisplayFramebuffer(framebuf.topaddr, framebuf.stride, framebuf.fmt);
550
__DisplayFlip(cyclesLate);
551
} else if (!flippedThisFrame) {
552
// Gotta flip even if sceDisplaySetFramebuf was not called.
553
__DisplayFlip(cyclesLate);
554
}
555
}
556
557
static void NotifyUserIfSlow() {
558
// Let the user know if we're running slow, so they know to adjust settings.
559
// Sometimes users just think the sound emulation is broken.
560
static bool hasNotifiedSlow = false;
561
if (!g_Config.bHideSlowWarnings &&
562
!hasNotifiedSlow &&
563
PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL &&
564
DisplayIsRunningSlow() && g_Config.bSoftwareRendering) {
565
#ifndef _DEBUG
566
auto err = GetI18NCategory(I18NCat::ERRORS);
567
g_OSD.Show(OSDType::MESSAGE_INFO, err->T("Running slow: Try turning off Software Rendering"), 5.0f);
568
#endif
569
hasNotifiedSlow = true;
570
}
571
}
572
573
static DisplayLayoutConfig g_displayLayoutConfigCached;
574
575
void __DisplaySetDisplayLayoutConfig(const DisplayLayoutConfig &config) {
576
g_displayLayoutConfigCached = config;
577
}
578
579
void __DisplayFlip(int cyclesLate) {
580
if (!gpu) {
581
_dbg_assert_(gpu);
582
// Something has gone wrong.
583
flippedThisFrame = true;
584
return;
585
}
586
587
__DisplaySetFramerate();
588
589
flippedThisFrame = true;
590
// We flip only if the framebuffer was dirty. This eliminates flicker when using
591
// non-buffered rendering. The interaction with frame skipping seems to need
592
// some work.
593
// But, let's flip at least once every 10 vblanks, to update fps, etc.
594
const bool noRecentFlip = !g_Config.bSkipBufferEffects && numVBlanksSinceFlip >= 10;
595
// Also let's always flip for animated shaders.
596
bool postEffectRequiresFlip = false;
597
598
bool duplicateFrames = g_Config.bRenderDuplicateFrames && g_Config.iFrameSkip == 0;
599
600
if (!g_Config.bSkipBufferEffects) {
601
postEffectRequiresFlip = duplicateFrames || g_Config.bShaderChainRequires60FPS;
602
}
603
604
if (!FrameTimingThrottled()) {
605
// NOTICE_LOG(Log::System, "Throttle: %d %d", (int)fastForwardSkipFlip, (int)postEffectRequiresFlip);
606
}
607
608
const bool fbDirty = gpu->FramebufferDirty();
609
610
Draw::DrawContext *draw = gpu->GetDrawContext();
611
612
bool needFlip = fbDirty || noRecentFlip || postEffectRequiresFlip;
613
if (!needFlip) {
614
// Okay, there's no new frame to draw, game might be sitting in a static loading screen
615
// or similar, and not long enough to trigger noRecentFlip. But audio may be playing, so we need to time still.
616
DoFrameIdleTiming();
617
g_frameTiming.ComputePresentMode(draw, false);
618
return;
619
}
620
621
// Debugger integration
622
int frameSleepPos = DisplayGetSleepPos();
623
double frameSleepStart = time_now_d();
624
DisplayFireFlip();
625
626
NotifyUserIfSlow();
627
628
bool forceNoFlip = false;
629
float refreshRate = System_GetPropertyFloat(SYSPROP_DISPLAY_REFRESH_RATE);
630
// Avoid skipping on devices that have 58 or 59 FPS, except when alternate speed is set.
631
const double fpsLimit = FrameTimingLimit();
632
bool throttle = fpsLimit != 0.0;
633
634
bool refreshRateNeedsSkip = (fpsLimit != framerate && fpsLimit > refreshRate) || !throttle;
635
636
g_frameTiming.ComputePresentMode(draw, refreshRateNeedsSkip);
637
638
// Alternative to frameskip fast-forward, where we draw everything.
639
// Useful if skipping a frame breaks graphics or for checking drawing speed.
640
if (g_frameTiming.FastForwardNeedsSkipFlip() && (!FrameTimingThrottled() || refreshRateNeedsSkip)) {
641
static double lastFlip = 0;
642
double now = time_now_d();
643
if ((now - lastFlip) < 1.0f / refreshRate) {
644
forceNoFlip = true;
645
} else {
646
lastFlip = now;
647
}
648
}
649
650
// Setting CORE_NEXTFRAME (which Core_NextFrame does) causes a swap.
651
const bool fbReallyDirty = gpu->FramebufferReallyDirty();
652
653
bool nextFrame = false;
654
655
if (fbReallyDirty || noRecentFlip || postEffectRequiresFlip) {
656
// Check first though, might've just quit / been paused.
657
if (!forceNoFlip) {
658
nextFrame = Core_NextFrame();
659
if (!nextFrame) {
660
WARN_LOG(Log::sceDisplay, "Core_NextFrame returned false");
661
}
662
}
663
if (nextFrame) {
664
gpu->SetCurFramebufferDirty(fbReallyDirty);
665
if (fbReallyDirty) {
666
DisplayFireActualFlip();
667
}
668
}
669
}
670
671
if (fbDirty) {
672
gpuStats.numFlips++;
673
}
674
675
float scaledTimestep = (float)numVBlanksSinceFlip * timePerVblank;
676
if (fpsLimit > 0 && fpsLimit != framerate) {
677
scaledTimestep *= (float)framerate / fpsLimit;
678
}
679
bool skipFrame;
680
DoFrameTiming(throttle, &skipFrame, scaledTimestep, nextFrame);
681
682
int maxFrameskip = 8;
683
const int frameSkipNum = g_Config.iFrameSkip;
684
if (throttle) {
685
// 4 here means 1 drawn, 4 skipped - so 12 fps minimum.
686
maxFrameskip = frameSkipNum;
687
}
688
if (numSkippedFrames >= maxFrameskip || gpuDebug->GetRecorder()->IsActivePending()) {
689
skipFrame = false;
690
}
691
692
if (skipFrame) {
693
// Tell the emulated GPU to skip the next frame.
694
gstate_c.skipDrawReason |= SKIPDRAW_SKIPFRAME;
695
numSkippedFrames++;
696
} else {
697
gstate_c.skipDrawReason &= ~SKIPDRAW_SKIPFRAME;
698
numSkippedFrames = 0;
699
700
// NOTE!! It can happen that if we just toggled frameskip (especially auto), we are still in a state
701
// where we don't have a framebuffer bound, from the last frame. But framebuffermanager still might think
702
// that we're in non-buffered mode.
703
if (gpu->GetFramebufferManagerCommon() && !gpu->GetFramebufferManagerCommon()->UseBufferedRendering() && !g_Config.bSkipBufferEffects) {
704
gpu->GetFramebufferManagerCommon()->ForceUseBufferedRendering(!g_Config.bSkipBufferEffects);
705
gstate_c.skipDrawReason &= ~SKIPDRAW_NON_DISPLAYED_FB;
706
}
707
}
708
709
// Returning here with coreState == CORE_NEXTFRAME causes a buffer flip to happen (next frame).
710
// Right after, we regain control for a little bit in hleAfterFlip. I think that's a great
711
// place to do housekeeping.
712
713
CoreTiming::ScheduleEvent(0 - cyclesLate, afterFlipEvent, 0);
714
numVBlanksSinceFlip = 0;
715
716
if ((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::FRAME_GRAPH || coreCollectDebugStats) {
717
// Track how long we sleep (whether vsync or sleep_ms.)
718
DisplayNotifySleep(time_now_d() - frameSleepStart, frameSleepPos);
719
}
720
}
721
722
void hleAfterFlip(u64 userdata, int cyclesLate) {
723
gpu->PSPFrame();
724
725
PPGeNotifyFrame();
726
727
// This seems like as good a time as any to check if the config changed.
728
if (lagSyncScheduled != UseLagSync()) {
729
ScheduleLagSync();
730
}
731
}
732
733
void hleLeaveVblank(u64 userdata, int cyclesLate) {
734
flippedThisFrame = false;
735
VERBOSE_LOG(Log::sceDisplay,"Leave VBlank %i", (int)userdata - 1);
736
CoreTiming::ScheduleEvent(msToCycles(frameMs - vblankMs) - cyclesLate, enterVblankEvent, userdata);
737
738
// Fire the vblank listeners after the vblank completes.
739
DisplayFireVblankEnd();
740
}
741
742
void hleLagSync(u64 userdata, int cyclesLate) {
743
// The goal here is to prevent network, audio, and input lag from the real world.
744
// Our normal timing is very "stop and go". This is efficient, but causes real world lag.
745
// This event (optionally) runs every 1ms to sync with the real world.
746
PROFILE_THIS_SCOPE("timing");
747
748
if (!FrameTimingThrottled()) {
749
lagSyncScheduled = false;
750
return;
751
}
752
753
float scale = 1.0f;
754
int fpsLimit = FrameTimingLimit();
755
if (fpsLimit != 0 && fpsLimit != framerate) {
756
// 0 is handled in FrameTimingThrottled().
757
scale = (float)framerate / fpsLimit;
758
}
759
760
const double goal = lastLagSync + (scale / 1000.0f);
761
double before = time_now_d();
762
// Don't lag too long ever, if they leave it paused.
763
double now = before;
764
while (now < goal && goal < now + 0.01) {
765
// Tight loop on win32 - intentionally, as timing is otherwise not precise enough.
766
// TODO: Use the precise waits if available
767
#ifndef _WIN32
768
const double left = goal - now;
769
if (left > 0.0f && left < 1.0f) { // Sanity check
770
usleep((long)(left * 1000000.0));
771
}
772
#else
773
yield();
774
#endif
775
now = time_now_d();
776
}
777
778
const int emuOver = (int)cyclesToUs(cyclesLate);
779
const int over = (int)((now - goal) * 1000000);
780
ScheduleLagSync(over - emuOver);
781
782
if ((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::FRAME_GRAPH || coreCollectDebugStats) {
783
DisplayNotifySleep(now - before);
784
}
785
}
786
787
static u32 sceDisplayIsVblank() {
788
return hleLogDebug(Log::sceDisplay, DisplayIsVblank());
789
}
790
791
void __DisplayWaitForVblanks(const char *reason, int vblanks, bool callbacks) {
792
const s64 ticksIntoFrame = CoreTiming::GetTicks() - DisplayFrameStartTicks();
793
const s64 cyclesToNextVblank = msToCycles(frameMs) - ticksIntoFrame;
794
795
// These syscalls take about 115 us, so if the next vblank is before then, we're waiting extra.
796
// At least, on real firmware a wait >= 16500 into the frame will wait two.
797
if (cyclesToNextVblank <= usToCycles(115)) {
798
++vblanks;
799
}
800
801
vblankWaitingThreads.push_back(WaitVBlankInfo(__KernelGetCurThread(), vblanks));
802
__KernelWaitCurThread(WAITTYPE_VBLANK, 1, 0, 0, callbacks, reason);
803
}
804
805
static u32 sceDisplaySetMode(int displayMode, int displayWidth, int displayHeight) {
806
if (displayMode != PSP_DISPLAY_MODE_LCD || displayWidth != 480 || displayHeight != 272) {
807
WARN_LOG_REPORT(Log::sceDisplay, "Video out requested, not supported: mode=%d size=%d,%d", displayMode, displayWidth, displayHeight);
808
}
809
if (displayMode != PSP_DISPLAY_MODE_LCD) {
810
return hleLogWarning(Log::sceDisplay, SCE_KERNEL_ERROR_INVALID_MODE, "invalid mode");
811
}
812
if (displayWidth != 480 || displayHeight != 272) {
813
return hleLogWarning(Log::sceDisplay, SCE_KERNEL_ERROR_INVALID_SIZE, "invalid size");
814
}
815
816
hasSetMode = true;
817
mode = displayMode;
818
width = displayWidth;
819
height = displayHeight;
820
821
// On success, this implicitly waits for a vblank start.
822
__DisplayWaitForVblanks("display mode", 1);
823
return hleLogDebug(Log::sceDisplay, 0);
824
}
825
826
void __DisplaySetFramebuf(u32 topaddr, int linesize, int pixelFormat, int sync) {
827
FrameBufferState fbstate = {0};
828
fbstate.topaddr = topaddr;
829
fbstate.fmt = (GEBufferFormat)pixelFormat;
830
fbstate.stride = linesize;
831
832
if (sync == PSP_DISPLAY_SETBUF_IMMEDIATE) {
833
// Write immediately to the current framebuffer parameters.
834
framebuf = fbstate;
835
// Also update latchedFramebuf for any sceDisplayGetFramebuf() after this.
836
latchedFramebuf = fbstate;
837
gpu->SetDisplayFramebuffer(framebuf.topaddr, framebuf.stride, framebuf.fmt);
838
// IMMEDIATE means that the buffer is fine. We can just flip immediately.
839
// Doing it in non-buffered though creates problems (black screen) on occasion though
840
// so let's not.
841
if (!flippedThisFrame && !g_Config.bSkipBufferEffects) {
842
double before_flip = time_now_d();
843
__DisplayFlip(0);
844
double after_flip = time_now_d();
845
// Ignore for debug stats.
846
hleSetFlipTime(after_flip - before_flip);
847
}
848
} else {
849
// Delay the write until vblank
850
latchedFramebuf = fbstate;
851
framebufIsLatched = true;
852
853
// If we update the format or stride, this affects the current framebuf immediately.
854
framebuf.fmt = latchedFramebuf.fmt;
855
framebuf.stride = latchedFramebuf.stride;
856
}
857
}
858
859
// Some games (GTA) never call this during gameplay, so bad place to put a framerate counter.
860
int sceDisplaySetFramebuf(u32 topaddr, int linesize, int pixelformat, int sync) {
861
if (sync != PSP_DISPLAY_SETBUF_IMMEDIATE && sync != PSP_DISPLAY_SETBUF_NEXTFRAME) {
862
return hleLogError(Log::sceDisplay, SCE_KERNEL_ERROR_INVALID_MODE, "invalid sync mode");
863
}
864
if (topaddr != 0 && !Memory::IsRAMAddress(topaddr) && !Memory::IsVRAMAddress(topaddr)) {
865
return hleLogError(Log::sceDisplay, SCE_KERNEL_ERROR_INVALID_POINTER, "invalid address");
866
}
867
if ((topaddr & 0xF) != 0) {
868
return hleLogError(Log::sceDisplay, SCE_KERNEL_ERROR_INVALID_POINTER, "misaligned address");
869
}
870
if ((linesize & 0x3F) != 0 || (linesize == 0 && topaddr != 0)) {
871
return hleLogWarning(Log::sceDisplay, SCE_KERNEL_ERROR_INVALID_SIZE, "invalid stride");
872
}
873
if (pixelformat < 0 || pixelformat > GE_FORMAT_8888) {
874
return hleLogError(Log::sceDisplay, SCE_KERNEL_ERROR_INVALID_FORMAT, "invalid format");
875
}
876
877
if (sync == PSP_DISPLAY_SETBUF_IMMEDIATE) {
878
if ((GEBufferFormat)pixelformat != latchedFramebuf.fmt || linesize != latchedFramebuf.stride) {
879
return hleReportWarning(Log::sceDisplay, SCE_KERNEL_ERROR_INVALID_MODE, "must change latched framebuf first");
880
}
881
}
882
883
hleEatCycles(290);
884
885
s64 delayCycles = 0;
886
// Don't count transitions between display off and display on.
887
if (topaddr != 0 &&
888
(topaddr != framebuf.topaddr || PSP_CoreParameter().compat.flags().SplitFramebufferMargin) &&
889
framebuf.topaddr != 0 &&
890
PSP_CoreParameter().compat.flags().ForceMax60FPS) {
891
// sceDisplaySetFramebuf() isn't supposed to delay threads at all. This is a hack.
892
// So let's only delay when it's more than 1ms.
893
const s64 FLIP_DELAY_CYCLES_MIN = usToCycles(1000);
894
// Some games (like Final Fantasy 4) only call this too much in spurts.
895
// The goal is to fix games where this would result in a consistent overhead.
896
const int FLIP_DELAY_MIN_FLIPS = 30;
897
// Since we move nextFlipCycles forward a whole frame each time, we allow it to be a little ahead.
898
// Otherwise it'll always be ahead if the game messes up even once.
899
const s64 LEEWAY_CYCLES_PER_FLIP = usToCycles(10);
900
901
u64 now = CoreTiming::GetTicks();
902
s64 cyclesAhead = nextFlipCycles - now;
903
if (cyclesAhead > FLIP_DELAY_CYCLES_MIN) {
904
if (lastFlipsTooFrequent >= FLIP_DELAY_MIN_FLIPS) {
905
delayCycles = cyclesAhead;
906
} else {
907
++lastFlipsTooFrequent;
908
}
909
} else if (-lastFlipsTooFrequent < FLIP_DELAY_MIN_FLIPS) {
910
--lastFlipsTooFrequent;
911
}
912
913
// 1001 to account for NTSC timing (59.94 fps.)
914
u64 expected = msToCycles(1001) / framerate - LEEWAY_CYCLES_PER_FLIP;
915
lastFlipCycles = now;
916
nextFlipCycles = std::max(lastFlipCycles, nextFlipCycles) + expected;
917
}
918
919
__DisplaySetFramebuf(topaddr, linesize, pixelformat, sync);
920
921
// No delaying while inside an interrupt. It'll cause idle threads to starve.
922
if (delayCycles > 0 && !__IsInInterrupt()) {
923
// Okay, the game is going at too high a frame rate. God of War and Fat Princess both do this.
924
// Simply eating the cycles works and is fast, but breaks other games (like Jeanne d'Arc.)
925
// So, instead, we delay this HLE thread only (a small deviation from correct behavior.)
926
return hleDelayResult(hleLogDebug(Log::sceDisplay, 0, "delaying frame thread"), "set framebuf", cyclesToUs(delayCycles));
927
} else {
928
if (topaddr == 0) {
929
return hleLogDebug(Log::sceDisplay, 0, "disabling display");
930
} else {
931
return hleLogDebug(Log::sceDisplay, 0);
932
}
933
}
934
}
935
936
bool __DisplayGetFramebuf(PSPPointer<u8> *topaddr, u32 *linesize, u32 *pixelFormat, int latchedMode) {
937
const FrameBufferState &fbState = latchedMode == PSP_DISPLAY_SETBUF_NEXTFRAME ? latchedFramebuf : framebuf;
938
if (topaddr != nullptr)
939
(*topaddr).ptr = fbState.topaddr;
940
if (linesize != nullptr)
941
*linesize = fbState.stride;
942
if (pixelFormat != nullptr)
943
*pixelFormat = fbState.fmt;
944
945
return true;
946
}
947
948
static u32 sceDisplayGetFramebuf(u32 topaddrPtr, u32 linesizePtr, u32 pixelFormatPtr, int latchedMode) {
949
const FrameBufferState &fbState = latchedMode == PSP_DISPLAY_SETBUF_NEXTFRAME ? latchedFramebuf : framebuf;
950
951
if (Memory::IsValidAddress(topaddrPtr))
952
Memory::Write_U32(fbState.topaddr, topaddrPtr);
953
if (Memory::IsValidAddress(linesizePtr))
954
Memory::Write_U32(fbState.stride, linesizePtr);
955
if (Memory::IsValidAddress(pixelFormatPtr))
956
Memory::Write_U32(fbState.fmt, pixelFormatPtr);
957
958
return hleLogDebug(Log::sceDisplay, 0);
959
}
960
961
static void __DisplayWaitForVblanksCB(const char *reason, int vblanks) {
962
__DisplayWaitForVblanks(reason, vblanks, true);
963
}
964
965
static int sceDisplayWaitVblankStart() {
966
__DisplayWaitForVblanks("vblank start waited", 1);
967
return hleLogDebug(Log::sceDisplay, 0);
968
}
969
970
static int sceDisplayWaitVblank() {
971
if (!DisplayIsVblank()) {
972
__DisplayWaitForVblanks("vblank waited", 1);
973
return hleLogDebug(Log::sceDisplay, 0);
974
} else {
975
hleEatCycles(1110);
976
hleReSchedule("vblank wait skipped");
977
return hleLogDebug(Log::sceDisplay, 1, "not waiting since in vblank");
978
}
979
}
980
981
static int sceDisplayWaitVblankStartMulti(int vblanks) {
982
if (vblanks <= 0) {
983
return hleLogWarning(Log::sceDisplay, SCE_KERNEL_ERROR_INVALID_VALUE, "invalid number of vblanks");
984
}
985
if (!__KernelIsDispatchEnabled())
986
return hleLogWarning(Log::sceDisplay, SCE_KERNEL_ERROR_CAN_NOT_WAIT, "dispatch disabled");
987
if (__IsInInterrupt())
988
return hleLogWarning(Log::sceDisplay, SCE_KERNEL_ERROR_ILLEGAL_CONTEXT, "in interrupt");
989
990
__DisplayWaitForVblanks("vblank start multi waited", vblanks);
991
return hleLogDebug(Log::sceDisplay, 0);
992
}
993
994
static int sceDisplayWaitVblankCB() {
995
if (!DisplayIsVblank()) {
996
__DisplayWaitForVblanksCB("vblank waited", 1);
997
return hleLogDebug(Log::sceDisplay, 0);
998
} else {
999
hleEatCycles(1110);
1000
hleReSchedule("vblank wait skipped");
1001
return hleLogDebug(Log::sceDisplay, 1, "not waiting since in vblank");
1002
}
1003
}
1004
1005
static int sceDisplayWaitVblankStartCB() {
1006
__DisplayWaitForVblanksCB("vblank start waited", 1);
1007
return hleLogDebug(Log::sceDisplay, 0);
1008
}
1009
1010
static int sceDisplayWaitVblankStartMultiCB(int vblanks) {
1011
if (vblanks <= 0) {
1012
return hleLogWarning(Log::sceDisplay, SCE_KERNEL_ERROR_INVALID_VALUE, "invalid number of vblanks");
1013
}
1014
if (!__KernelIsDispatchEnabled())
1015
return hleLogWarning(Log::sceDisplay, SCE_KERNEL_ERROR_CAN_NOT_WAIT, "dispatch disabled");
1016
if (__IsInInterrupt())
1017
return hleLogWarning(Log::sceDisplay, SCE_KERNEL_ERROR_ILLEGAL_CONTEXT, "in interrupt");
1018
1019
__DisplayWaitForVblanksCB("vblank start multi waited", vblanks);
1020
return hleLogDebug(Log::sceDisplay, 0);
1021
}
1022
1023
static int sceDisplayGetVcount() {
1024
hleEatCycles(150);
1025
hleReSchedule("get vcount");
1026
return hleLogVerbose(Log::sceDisplay, __DisplayGetVCount());
1027
}
1028
1029
static int sceDisplayGetCurrentHcount() {
1030
hleEatCycles(275);
1031
return hleLogDebug(Log::sceDisplay, __DisplayGetCurrentHcount());
1032
}
1033
1034
static int sceDisplayAdjustAccumulatedHcount(int value) {
1035
if (value < 0) {
1036
return hleLogError(Log::sceDisplay, SCE_KERNEL_ERROR_INVALID_VALUE, "invalid value");
1037
}
1038
1039
// Since it includes the current hCount, find the difference to apply to the base.
1040
u32 accumHCount = __DisplayGetAccumulatedHcount();
1041
int diff = value - accumHCount;
1042
DisplayAdjustAccumulatedHcount(diff);
1043
1044
return hleLogDebug(Log::sceDisplay, 0);
1045
}
1046
1047
static int sceDisplayGetAccumulatedHcount() {
1048
u32 accumHCount = __DisplayGetAccumulatedHcount();
1049
hleEatCycles(235);
1050
return hleLogDebug(Log::sceDisplay, accumHCount);
1051
}
1052
1053
static float sceDisplayGetFramePerSec() {
1054
const static float framePerSec = 59.9400599f;
1055
return hleLogVerbose(Log::sceDisplay, framePerSec); // (9MHz * 1)/(525 * 286)
1056
}
1057
1058
static u32 sceDisplayIsForeground() {
1059
int result = hasSetMode && framebuf.topaddr != 0 ? 1 : 0;
1060
return hleLogDebug(Log::sceDisplay, result);
1061
}
1062
1063
static u32 sceDisplayGetMode(u32 modeAddr, u32 widthAddr, u32 heightAddr) {
1064
if (Memory::IsValidAddress(modeAddr))
1065
Memory::Write_U32(mode, modeAddr);
1066
if (Memory::IsValidAddress(widthAddr))
1067
Memory::Write_U32(width, widthAddr);
1068
if (Memory::IsValidAddress(heightAddr))
1069
Memory::Write_U32(height, heightAddr);
1070
return hleLogDebug(Log::sceDisplay, 0);
1071
}
1072
1073
static u32 sceDisplayIsVsync() {
1074
u64 now = CoreTiming::GetTicks();
1075
u64 start = DisplayFrameStartTicks() + msToCycles(vsyncStartMs);
1076
u64 end = DisplayFrameStartTicks() + msToCycles(vsyncEndMs);
1077
1078
return hleLogDebug(Log::sceDisplay, now >= start && now <= end ? 1 : 0);
1079
}
1080
1081
static u32 sceDisplayGetResumeMode(u32 resumeModeAddr) {
1082
if (Memory::IsValidAddress(resumeModeAddr))
1083
Memory::Write_U32(resumeMode, resumeModeAddr);
1084
return hleLogDebug(Log::sceDisplay, 0);
1085
}
1086
1087
static u32 sceDisplaySetResumeMode(u32 rMode) {
1088
// Not sure what this does, seems to do nothing in tests and accept all values.
1089
resumeMode = rMode;
1090
return hleReportError(Log::sceDisplay, 0, "unsupported");
1091
}
1092
1093
static u32 sceDisplayGetBrightness(u32 levelAddr, u32 otherAddr) {
1094
// Standard levels on a PSP: 44, 60, 72, 84 (AC only)
1095
1096
if (Memory::IsValidAddress(levelAddr)) {
1097
Memory::Write_U32(brightnessLevel, levelAddr);
1098
}
1099
// Always seems to write zero?
1100
if (Memory::IsValidAddress(otherAddr)) {
1101
Memory::Write_U32(0, otherAddr);
1102
}
1103
return hleLogWarning(Log::sceDisplay, 0);
1104
}
1105
1106
static u32 sceDisplaySetBrightness(int level, int other) {
1107
// Note: Only usable in kernel mode.
1108
brightnessLevel = level;
1109
return hleLogWarning(Log::sceDisplay, 0);
1110
}
1111
1112
static u32 sceDisplaySetHoldMode(u32 hMode) {
1113
// Not sure what this does, seems to do nothing in tests and accept all values.
1114
holdMode = hMode;
1115
return hleLogWarning(Log::sceDisplay, 0, "UNIMPL");
1116
}
1117
1118
const HLEFunction sceDisplay[] = {
1119
{0X0E20F177, &WrapU_III<sceDisplaySetMode>, "sceDisplaySetMode", 'x', "iii" },
1120
{0X289D82FE, &WrapI_UIII<sceDisplaySetFramebuf>, "sceDisplaySetFrameBuf", 'i', "xiii"},
1121
{0XEEDA2E54, &WrapU_UUUI<sceDisplayGetFramebuf>, "sceDisplayGetFrameBuf", 'x', "pppi"},
1122
{0X36CDFADE, &WrapI_V<sceDisplayWaitVblank>, "sceDisplayWaitVblank", 'i', "", HLE_NOT_DISPATCH_SUSPENDED },
1123
{0X984C27E7, &WrapI_V<sceDisplayWaitVblankStart>, "sceDisplayWaitVblankStart", 'i', "", HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED },
1124
{0X40F1469C, &WrapI_I<sceDisplayWaitVblankStartMulti>, "sceDisplayWaitVblankStartMulti", 'i', "i" },
1125
{0X8EB9EC49, &WrapI_V<sceDisplayWaitVblankCB>, "sceDisplayWaitVblankCB", 'i', "", HLE_NOT_DISPATCH_SUSPENDED },
1126
{0X46F186C3, &WrapI_V<sceDisplayWaitVblankStartCB>, "sceDisplayWaitVblankStartCB", 'i', "", HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED },
1127
{0X77ED8B3A, &WrapI_I<sceDisplayWaitVblankStartMultiCB>, "sceDisplayWaitVblankStartMultiCB", 'i', "i" },
1128
{0XDBA6C4C4, &WrapF_V<sceDisplayGetFramePerSec>, "sceDisplayGetFramePerSec", 'f', "" },
1129
{0X773DD3A3, &WrapI_V<sceDisplayGetCurrentHcount>, "sceDisplayGetCurrentHcount", 'i', "" },
1130
{0X210EAB3A, &WrapI_V<sceDisplayGetAccumulatedHcount>, "sceDisplayGetAccumulatedHcount", 'i', "" },
1131
{0XA83EF139, &WrapI_I<sceDisplayAdjustAccumulatedHcount>, "sceDisplayAdjustAccumulatedHcount", 'i', "i" },
1132
{0X9C6EAAD7, &WrapI_V<sceDisplayGetVcount>, "sceDisplayGetVcount", 'i', "" },
1133
{0XDEA197D4, &WrapU_UUU<sceDisplayGetMode>, "sceDisplayGetMode", 'x', "ppp" },
1134
{0X7ED59BC4, &WrapU_U<sceDisplaySetHoldMode>, "sceDisplaySetHoldMode", 'x', "x" },
1135
{0XA544C486, &WrapU_U<sceDisplaySetResumeMode>, "sceDisplaySetResumeMode", 'x', "x" },
1136
{0XBF79F646, &WrapU_U<sceDisplayGetResumeMode>, "sceDisplayGetResumeMode", 'x', "p" },
1137
{0XB4F378FA, &WrapU_V<sceDisplayIsForeground>, "sceDisplayIsForeground", 'x', "" },
1138
{0X31C4BAA8, &WrapU_UU<sceDisplayGetBrightness>, "sceDisplayGetBrightness", 'x', "pp" },
1139
{0X9E3C6DC6, &WrapU_II<sceDisplaySetBrightness>, "sceDisplaySetBrightness", 'x', "ii" },
1140
{0X4D4E10EC, &WrapU_V<sceDisplayIsVblank>, "sceDisplayIsVblank", 'x', "" },
1141
{0X21038913, &WrapU_V<sceDisplayIsVsync>, "sceDisplayIsVsync", 'x', "" },
1142
};
1143
1144
void Register_sceDisplay() {
1145
RegisterHLEModule("sceDisplay", ARRAY_SIZE(sceDisplay), sceDisplay);
1146
}
1147
1148
void Register_sceDisplay_driver() {
1149
RegisterHLEModule("sceDisplay_driver", ARRAY_SIZE(sceDisplay), sceDisplay);
1150
}
1151
1152
static void __DisplaySetFramerate(void) {
1153
if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_VR)
1154
framerate = g_Config.bForce72Hz ? 72 : 60;
1155
else
1156
framerate = g_Config.iDisplayRefreshRate;
1157
1158
timePerVblank = 1.001 / (double)framerate;
1159
frameMs = 1001.0 / (double)framerate;
1160
}
1161
1162