CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

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