From ccfbc5fcd090590e0278db1aa93d9bfe80d09641 Mon Sep 17 00:00:00 2001 From: Xie Yi Date: Sun, 9 Jun 2019 22:15:39 +0800 Subject: [PATCH 01/20] Show root view controller on external display --- ios/AppDelegate.h | 1 + ios/AppDelegate.mm | 30 +++++++++++++++++++++--------- ios/ViewController.mm | 17 ++++++++++------- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/ios/AppDelegate.h b/ios/AppDelegate.h index 263e85d369..feb65efbf4 100644 --- a/ios/AppDelegate.h +++ b/ios/AppDelegate.h @@ -7,6 +7,7 @@ @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; +@property (strong, nonatomic) UIScreen *screen; @property (strong, nonatomic) ViewController *viewController; diff --git a/ios/AppDelegate.mm b/ios/AppDelegate.mm index 5ae6e18003..34b90b2bbe 100644 --- a/ios/AppDelegate.mm +++ b/ios/AppDelegate.mm @@ -72,15 +72,27 @@ } -(BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; - self.viewController = [[ViewController alloc] init]; - - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAudioSessionInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]]; - - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMediaServicesWereReset:) name:AVAudioSessionMediaServicesWereResetNotification object:nil]; - - self.window.rootViewController = self.viewController; - [self.window makeKeyAndVisible]; + + [[NSNotificationCenter defaultCenter] addObserverForName:UIScreenDidConnectNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull notification) { + UIScreen* screen = (UIScreen*)notification.object; + self.screen = screen; + + NSLog(@"Display connected: %@", screen.debugDescription); + UIScreenMode* mode = screen.availableModes[screen.availableModes.count - 1]; + [screen setCurrentMode:mode]; + [screen setOverscanCompensation:UIScreenOverscanCompensationNone]; + + self.window = [[UIWindow alloc] initWithFrame:[screen bounds]]; + [self.window setScreen:screen]; + self.viewController = [[ViewController alloc] init]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAudioSessionInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMediaServicesWereReset:) name:AVAudioSessionMediaServicesWereResetNotification object:nil]; + + self.window.rootViewController = self.viewController; + [self.window makeKeyAndVisible]; + }]; return YES; } diff --git a/ios/ViewController.mm b/ios/ViewController.mm index 5eba171e2b..8ebae24cdc 100644 --- a/ios/ViewController.mm +++ b/ios/ViewController.mm @@ -5,6 +5,7 @@ // Modified by xSacha // +#import "AppDelegate.h" #import "ViewController.h" #import "SubtleVolume.h" #import @@ -144,7 +145,8 @@ static GraphicsContext *graphicsContext; - (void)viewDidLoad { [super viewDidLoad]; - self.view.frame = [[UIScreen mainScreen] bounds]; + UIScreen* screen = [(AppDelegate*)[UIApplication sharedApplication].delegate screen]; + self.view.frame = [screen bounds]; self.view.multipleTouchEnabled = YES; self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; @@ -159,12 +161,13 @@ static GraphicsContext *graphicsContext; self.preferredFramesPerSecond = 60; // Might be useful for a speed boot, sacrificing resolution: - // view.contentScaleFactor = 1.0; + view.contentScaleFactor = screen.scale; - float scale = [UIScreen mainScreen].scale; + float scale = screen.scale; + - if ([[UIScreen mainScreen] respondsToSelector:@selector(nativeScale)]) { - scale = [UIScreen mainScreen].nativeScale; + if ([screen respondsToSelector:@selector(nativeScale)]) { + scale = screen.nativeScale; } CGSize size = [[UIApplication sharedApplication].delegate window].frame.size; @@ -176,8 +179,8 @@ static GraphicsContext *graphicsContext; } g_dpi = (IS_IPAD() ? 200.0f : 150.0f) * scale; - g_dpi_scale_x = 240.0f / g_dpi; - g_dpi_scale_y = 240.0f / g_dpi; + g_dpi_scale_x = 1;//240.0f / g_dpi; + g_dpi_scale_y = 1;//240.0f / g_dpi; g_dpi_scale_real_x = g_dpi_scale_x; g_dpi_scale_real_y = g_dpi_scale_y; pixel_xres = size.width * scale; From 259f0ce22e41b8feab64333c2e26bd02e3e2ed7d Mon Sep 17 00:00:00 2001 From: Xie Yi Date: Tue, 11 Jun 2019 01:24:58 +0800 Subject: [PATCH 02/20] Auto switch between internal and external display --- CMakeLists.txt | 2 + ios/AppDelegate.mm | 31 +++------- ios/DisplayManager.h | 24 ++++++++ ios/DisplayManager.mm | 139 ++++++++++++++++++++++++++++++++++++++++++ ios/ViewController.mm | 31 +--------- 5 files changed, 178 insertions(+), 49 deletions(-) create mode 100644 ios/DisplayManager.h create mode 100644 ios/DisplayManager.mm diff --git a/CMakeLists.txt b/CMakeLists.txt index 89ec8303c5..3735f82025 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -735,6 +735,8 @@ elseif(IOS) ios/main.mm ios/AppDelegate.mm ios/AppDelegate.h + ios/DisplayManager.h + ios/DisplayManager.mm ios/ViewController.mm ios/ViewController.h ios/iOSCoreAudio.mm diff --git a/ios/AppDelegate.mm b/ios/AppDelegate.mm index 34b90b2bbe..4cbd2ae1fb 100644 --- a/ios/AppDelegate.mm +++ b/ios/AppDelegate.mm @@ -1,4 +1,5 @@ #import "AppDelegate.h" +#import "DisplayManager.h" #import "ViewController.h" #import "base/NativeApp.h" #import "Core/System.h" @@ -72,27 +73,15 @@ } -(BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - - [[NSNotificationCenter defaultCenter] addObserverForName:UIScreenDidConnectNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull notification) { - UIScreen* screen = (UIScreen*)notification.object; - self.screen = screen; - - NSLog(@"Display connected: %@", screen.debugDescription); - UIScreenMode* mode = screen.availableModes[screen.availableModes.count - 1]; - [screen setCurrentMode:mode]; - [screen setOverscanCompensation:UIScreenOverscanCompensationNone]; - - self.window = [[UIWindow alloc] initWithFrame:[screen bounds]]; - [self.window setScreen:screen]; - self.viewController = [[ViewController alloc] init]; - - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAudioSessionInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]]; - - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMediaServicesWereReset:) name:AVAudioSessionMediaServicesWereResetNotification object:nil]; - - self.window.rootViewController = self.viewController; - [self.window makeKeyAndVisible]; - }]; + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.viewController = [[ViewController alloc] init]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAudioSessionInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMediaServicesWereReset:) name:AVAudioSessionMediaServicesWereResetNotification object:nil]; + + self.window.rootViewController = self.viewController; + [self.window makeKeyAndVisible]; return YES; } diff --git a/ios/DisplayManager.h b/ios/DisplayManager.h new file mode 100644 index 0000000000..fb46707a19 --- /dev/null +++ b/ios/DisplayManager.h @@ -0,0 +1,24 @@ +// +// DisplayManager.h +// native +// +// Created by xieyi on 2019/6/9. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface DisplayManager : NSObject + +- (void)setupDisplayListener; +- (void)updateResolution:(UIScreen *)screen; + +@property (nonatomic, strong) UIScreen *mainScreen; + +@property (class, readonly, strong) DisplayManager *shared; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/DisplayManager.mm b/ios/DisplayManager.mm new file mode 100644 index 0000000000..5b20d8a252 --- /dev/null +++ b/ios/DisplayManager.mm @@ -0,0 +1,139 @@ +// +// DisplayManager.m +// native +// +// Created by xieyi on 2019/6/9. +// + +#import "DisplayManager.h" +#import "ViewController.h" +#import "AppDelegate.h" +#include "base/display.h" +#include "base/NativeApp.h" +#include "Core/System.h" + +#define IS_IPAD() ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) + +@interface DisplayManager () + +@property BOOL listenerActive; +@property (atomic, retain) NSMutableArray *extDisplays; + +- (void)updateScreen:(UIScreen *)screen; + +@end + +@implementation DisplayManager + +- (instancetype)init +{ + self = [super init]; + if (self) { + [self setListenerActive:NO]; + [self setExtDisplays:[[NSMutableArray alloc] init]]; + } + return self; +} + ++ (DisplayManager *)shared { + static DisplayManager *sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[DisplayManager alloc] init]; + }); + return sharedInstance; +} + +- (void)setupDisplayListener { + if ([self listenerActive]) { + NSLog(@"setupDisplayListener already called"); + return; + } + NSLog(@"Setting up display manager"); + [self setMainScreen:[UIScreen mainScreen]]; + // Display connected + [[NSNotificationCenter defaultCenter] addObserverForName:UIScreenDidConnectNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull notification) { + UIScreen *screen = (UIScreen *) notification.object; + NSLog(@"New display connected: %@", [screen debugDescription]); + [[self extDisplays] addObject:screen]; + // Do not switch to second connected display + if ([self mainScreen] != [UIScreen mainScreen]) { + return; + } + [self updateScreen:screen]; + }]; + // Display disconnected + [[NSNotificationCenter defaultCenter] addObserverForName:UIScreenDidDisconnectNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull notification) { + UIScreen *screen = (UIScreen *) notification.object; + NSLog(@"Display disconnected: %@", [screen debugDescription]); + if ([[self extDisplays] containsObject:screen]) { + [[self extDisplays] removeObject:screen]; + } + if ([[self extDisplays] count] > 0) { + UIScreen *newScreen = [[self extDisplays] lastObject]; + [self updateScreen:newScreen]; + } else { + [self updateScreen:[UIScreen mainScreen]]; + } + }]; + [self setListenerActive:YES]; +} + +- (void)updateScreen:(UIScreen *)screen { + [self setMainScreen:screen]; + UIWindow *gameWindow = [(AppDelegate *)[[UIApplication sharedApplication] delegate] window]; + [gameWindow setScreen:screen]; + // Set optimal resolution + // Dispatch later to prevent "no window is preset" error + dispatch_async(dispatch_get_main_queue(), ^{ + if (screen != [UIScreen mainScreen]) { + NSUInteger count = [[screen availableModes] count]; + UIScreenMode* mode = [screen availableModes][count - 1]; + [screen setCurrentMode:mode]; + [screen setOverscanCompensation:UIScreenOverscanCompensationNone]; + } + [self updateResolution:screen]; + }); +} + +- (void)updateResolution:(UIScreen *)screen { + float scale = screen.scale; + + if ([screen respondsToSelector:@selector(nativeScale)]) { + scale = screen.nativeScale; + } + + CGSize size = screen.applicationFrame.size; + + if (size.height > size.width) { + float h = size.height; + size.height = size.width; + size.width = h; + } + + g_dpi = (IS_IPAD() ? 200.0f : 150.0f) * scale; + g_dpi_scale_x = 240.0f / g_dpi; + g_dpi_scale_y = 240.0f / g_dpi; + g_dpi_scale_real_x = g_dpi_scale_x; + g_dpi_scale_real_y = g_dpi_scale_y; + pixel_xres = size.width * scale; + pixel_yres = size.height * scale; + + dp_xres = pixel_xres * g_dpi_scale_x; + dp_yres = pixel_yres * g_dpi_scale_y; + + pixel_in_dps_x = (float)pixel_xres / (float)dp_xres; + pixel_in_dps_y = (float)pixel_yres / (float)dp_yres; + + [[sharedViewController view] setContentScaleFactor:scale]; + + // PSP native resize + PSP_CoreParameter().pixelWidth = pixel_xres; + PSP_CoreParameter().pixelHeight = pixel_yres; + + NativeResized(); + + NSLog(@"Updated display resolution: (%d, %d) @%.1fx", pixel_xres, pixel_yres, scale); +} + +@end diff --git a/ios/ViewController.mm b/ios/ViewController.mm index 8ebae24cdc..457e331a74 100644 --- a/ios/ViewController.mm +++ b/ios/ViewController.mm @@ -7,6 +7,7 @@ #import "AppDelegate.h" #import "ViewController.h" +#import "DisplayManager.h" #import "SubtleVolume.h" #import #include @@ -144,6 +145,7 @@ static GraphicsContext *graphicsContext; - (void)viewDidLoad { [super viewDidLoad]; + [[DisplayManager shared] setupDisplayListener]; UIScreen* screen = [(AppDelegate*)[UIApplication sharedApplication].delegate screen]; self.view.frame = [screen bounds]; @@ -163,34 +165,7 @@ static GraphicsContext *graphicsContext; // Might be useful for a speed boot, sacrificing resolution: view.contentScaleFactor = screen.scale; - float scale = screen.scale; - - - if ([screen respondsToSelector:@selector(nativeScale)]) { - scale = screen.nativeScale; - } - - CGSize size = [[UIApplication sharedApplication].delegate window].frame.size; - - if (size.height > size.width) { - float h = size.height; - size.height = size.width; - size.width = h; - } - - g_dpi = (IS_IPAD() ? 200.0f : 150.0f) * scale; - g_dpi_scale_x = 1;//240.0f / g_dpi; - g_dpi_scale_y = 1;//240.0f / g_dpi; - g_dpi_scale_real_x = g_dpi_scale_x; - g_dpi_scale_real_y = g_dpi_scale_y; - pixel_xres = size.width * scale; - pixel_yres = size.height * scale; - - dp_xres = pixel_xres * g_dpi_scale_x; - dp_yres = pixel_yres * g_dpi_scale_y; - - pixel_in_dps_x = (float)pixel_xres / (float)dp_xres; - pixel_in_dps_y = (float)pixel_yres / (float)dp_yres; + [[DisplayManager shared] updateResolution:[UIScreen mainScreen]]; graphicsContext = new IOSGraphicsContext(); From bed47ea30a192be07b88f4e646744b453cc66582 Mon Sep 17 00:00:00 2001 From: Xie Yi Date: Tue, 11 Jun 2019 02:30:19 +0800 Subject: [PATCH 03/20] =?UTF-8?q?Fix=20iPhone=20won=E2=80=99t=20switch=20t?= =?UTF-8?q?o=20external=20display?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/DisplayManager.mm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ios/DisplayManager.mm b/ios/DisplayManager.mm index 5b20d8a252..eeff3f1069 100644 --- a/ios/DisplayManager.mm +++ b/ios/DisplayManager.mm @@ -82,6 +82,8 @@ - (void)updateScreen:(UIScreen *)screen { [self setMainScreen:screen]; UIWindow *gameWindow = [(AppDelegate *)[[UIApplication sharedApplication] delegate] window]; + // Hide before moving window to external display, otherwise iPhone won't switch to it + [gameWindow setHidden:YES]; [gameWindow setScreen:screen]; // Set optimal resolution // Dispatch later to prevent "no window is preset" error @@ -93,6 +95,7 @@ [screen setOverscanCompensation:UIScreenOverscanCompensationNone]; } [self updateResolution:screen]; + [gameWindow setHidden:NO]; }); } From d5d76b086fc193a057f9ea821bcfa260ff0d1056 Mon Sep 17 00:00:00 2001 From: Xie Yi Date: Tue, 11 Jun 2019 02:48:18 +0800 Subject: [PATCH 04/20] Do some clean up --- ios/AppDelegate.mm | 1 - ios/ViewController.mm | 3 --- 2 files changed, 4 deletions(-) diff --git a/ios/AppDelegate.mm b/ios/AppDelegate.mm index 4cbd2ae1fb..5ae6e18003 100644 --- a/ios/AppDelegate.mm +++ b/ios/AppDelegate.mm @@ -1,5 +1,4 @@ #import "AppDelegate.h" -#import "DisplayManager.h" #import "ViewController.h" #import "base/NativeApp.h" #import "Core/System.h" diff --git a/ios/ViewController.mm b/ios/ViewController.mm index 457e331a74..bdcf92e7d5 100644 --- a/ios/ViewController.mm +++ b/ios/ViewController.mm @@ -162,9 +162,6 @@ static GraphicsContext *graphicsContext; [EAGLContext setCurrentContext:self.context]; self.preferredFramesPerSecond = 60; - // Might be useful for a speed boot, sacrificing resolution: - view.contentScaleFactor = screen.scale; - [[DisplayManager shared] updateResolution:[UIScreen mainScreen]]; graphicsContext = new IOSGraphicsContext(); From a68c982b85c9aeea3d38e3df7775bc4486d1eaba Mon Sep 17 00:00:00 2001 From: Xie Yi Date: Tue, 11 Jun 2019 05:44:58 +0800 Subject: [PATCH 05/20] Fix overscan compensation (Hack) --- ios/DisplayManager.mm | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/ios/DisplayManager.mm b/ios/DisplayManager.mm index eeff3f1069..bdaafd48bd 100644 --- a/ios/DisplayManager.mm +++ b/ios/DisplayManager.mm @@ -18,6 +18,9 @@ @property BOOL listenerActive; @property (atomic, retain) NSMutableArray *extDisplays; +@property CGRect originalFrame; +@property CGRect originalBounds; +@property CGAffineTransform originalTransform; - (void)updateScreen:(UIScreen *)screen; @@ -51,6 +54,10 @@ } NSLog(@"Setting up display manager"); [self setMainScreen:[UIScreen mainScreen]]; + UIWindow *gameWindow = [(AppDelegate *)[[UIApplication sharedApplication] delegate] window]; + [self setOriginalFrame: [gameWindow frame]]; + [self setOriginalBounds:[gameWindow bounds]]; + [self setOriginalTransform:[gameWindow transform]]; // Display connected [[NSNotificationCenter defaultCenter] addObserverForName:UIScreenDidConnectNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull notification) { UIScreen *screen = (UIScreen *) notification.object; @@ -92,9 +99,23 @@ NSUInteger count = [[screen availableModes] count]; UIScreenMode* mode = [screen availableModes][count - 1]; [screen setCurrentMode:mode]; + // Fix overscan + // TODO: Hacky solution. Screen is still scaled even if UIScreenOverscanCompensationNone is set. [screen setOverscanCompensation:UIScreenOverscanCompensationNone]; + CGSize fullSize = mode.size; + UIEdgeInsets insets = [screen overscanCompensationInsets]; + fullSize.width -= insets.left + insets.right; + fullSize.height -= insets.top + insets.bottom; + [gameWindow setFrame:CGRectMake(insets.left, insets.top, fullSize.width, fullSize.height)]; + [gameWindow setBounds:CGRectMake(0, 0, fullSize.width, fullSize.height)]; + [self updateResolution:screen]; + [gameWindow setTransform:CGAffineTransformMakeScale(mode.size.width / fullSize.width, mode.size.height / fullSize.height)]; + } else { + [gameWindow setTransform:[self originalTransform]]; + [gameWindow setFrame:[self originalFrame]]; + [gameWindow setBounds:[self originalBounds]]; + [self updateResolution:screen]; } - [self updateResolution:screen]; [gameWindow setHidden:NO]; }); } From 364b447ee4c70eb5de934937d4da8e68afc5678b Mon Sep 17 00:00:00 2001 From: Xie Yi Date: Tue, 11 Jun 2019 14:04:18 +0800 Subject: [PATCH 06/20] Simple dpi scaling --- ios/DisplayManager.mm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ios/DisplayManager.mm b/ios/DisplayManager.mm index bdaafd48bd..94a7a69bad 100644 --- a/ios/DisplayManager.mm +++ b/ios/DisplayManager.mm @@ -135,7 +135,12 @@ size.width = h; } - g_dpi = (IS_IPAD() ? 200.0f : 150.0f) * scale; + if (screen == [UIScreen mainScreen]) { + g_dpi = (IS_IPAD() ? 200.0f : 150.0f) * scale; + } else { + float diagonal = sqrt(size.height * size.height + size.width * size.width); + g_dpi = diagonal * scale * 0.1f; + } g_dpi_scale_x = 240.0f / g_dpi; g_dpi_scale_y = 240.0f / g_dpi; g_dpi_scale_real_x = g_dpi_scale_x; From 1affe0bff3c62fbdc6235e061d40d82eecf71785 Mon Sep 17 00:00:00 2001 From: Xie Yi Date: Tue, 11 Jun 2019 14:44:06 +0800 Subject: [PATCH 07/20] Fix shutdown because of Discord deinitializer https://github.com/hrydgard/ppsspp/blob/f94beb518504eebadb94dfdc009cf9f4461bc6a4/UI/DiscordIntegration.cpp#L40 Calling exit(0) without NativeShutdown() breaks the assertion. --- ios/ViewController.mm | 3 ++- ios/main.mm | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ios/ViewController.mm b/ios/ViewController.mm index bdcf92e7d5..ad334f0d8d 100644 --- a/ios/ViewController.mm +++ b/ios/ViewController.mm @@ -213,7 +213,6 @@ static GraphicsContext *graphicsContext; time_update(); } - threadStopped = true; ILOG("Emulation thread shutting down\n"); NativeShutdownGraphics(); @@ -221,6 +220,8 @@ static GraphicsContext *graphicsContext; // Also ask the main thread to stop, so it doesn't hang waiting for a new frame. ILOG("Emulation thread stopping\n"); graphicsContext->StopThread(); + + threadStopped = true; }); } diff --git a/ios/main.mm b/ios/main.mm index ce1803e023..e52f3f8bf9 100644 --- a/ios/main.mm +++ b/ios/main.mm @@ -61,7 +61,10 @@ bool System_GetPropertyBool(SystemProperty prop) { void System_SendMessage(const char *command, const char *parameter) { if (!strcmp(command, "finish")) { - exit(0); + dispatch_async(dispatch_get_main_queue(), ^{ + [sharedViewController shutdown]; + exit(0); + }); } } From 4a7e9d47e3e62810f748cf0db7be509c76cb3092 Mon Sep 17 00:00:00 2001 From: Xie Yi Date: Tue, 11 Jun 2019 15:09:42 +0800 Subject: [PATCH 08/20] Ignore mute switch when connected to external display --- ios/DisplayManager.mm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ios/DisplayManager.mm b/ios/DisplayManager.mm index 94a7a69bad..5aca0161c9 100644 --- a/ios/DisplayManager.mm +++ b/ios/DisplayManager.mm @@ -11,6 +11,7 @@ #include "base/display.h" #include "base/NativeApp.h" #include "Core/System.h" +#import #define IS_IPAD() ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) @@ -67,6 +68,9 @@ if ([self mainScreen] != [UIScreen mainScreen]) { return; } + // Ignore mute switch when connected to external display + NSError *error = nil; + [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error]; [self updateScreen:screen]; }]; // Display disconnected @@ -80,6 +84,8 @@ UIScreen *newScreen = [[self extDisplays] lastObject]; [self updateScreen:newScreen]; } else { + NSError *error = nil; + [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:&error]; [self updateScreen:[UIScreen mainScreen]]; } }]; From c0a1404308ceb19ccf49af304128d85329b8a97e Mon Sep 17 00:00:00 2001 From: Xie Yi Date: Thu, 13 Jun 2019 22:57:19 +0800 Subject: [PATCH 09/20] Fix invalid mode on iPad Pro 2018 When connected to 4K display via C2HDMI cable, the last item in availableModes is 5K. --- ios/DisplayManager.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/ios/DisplayManager.mm b/ios/DisplayManager.mm index 5aca0161c9..1bd04aa1a1 100644 --- a/ios/DisplayManager.mm +++ b/ios/DisplayManager.mm @@ -105,6 +105,7 @@ NSUInteger count = [[screen availableModes] count]; UIScreenMode* mode = [screen availableModes][count - 1]; [screen setCurrentMode:mode]; + mode = [screen currentMode]; // Fix overscan // TODO: Hacky solution. Screen is still scaled even if UIScreenOverscanCompensationNone is set. [screen setOverscanCompensation:UIScreenOverscanCompensationNone]; From 0e7c85d89e6635005dc35f52a3d095e904abdea9 Mon Sep 17 00:00:00 2001 From: Xie Yi Date: Wed, 19 Jun 2019 17:47:14 +0800 Subject: [PATCH 10/20] Disable external display by default Configuration is in system settings --- CMakeLists.txt | 2 ++ ios/DisplayManager.mm | 4 ++++ ios/Settings.bundle/Root.plist | 21 +++++++++++++++++++++ ios/Settings.bundle/en.lproj/Root.strings | Bin 0 -> 480 bytes 4 files changed, 27 insertions(+) create mode 100644 ios/Settings.bundle/Root.plist create mode 100644 ios/Settings.bundle/en.lproj/Root.strings diff --git a/CMakeLists.txt b/CMakeLists.txt index 3735f82025..547d322307 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1988,6 +1988,7 @@ if(TargetBin) endif() if(IOS) + list(APPEND NativeAssets ios/Settings.bundle) add_executable(${TargetBin} MACOSX_BUNDLE ${ICON_PATH_ABS} ${NativeAssets} ${SHADER_FILES} ${FLASH0_FILES} ${LANG_FILES} ${NativeAppSource} "ios/Launch Screen.storyboard") else() add_executable(${TargetBin} MACOSX_BUNDLE ${ICON_PATH_ABS} ${NativeAssets} ${SHADER_FILES} ${FLASH0_FILES} ${LANG_FILES} ${NativeAppSource}) @@ -2044,6 +2045,7 @@ if(IOS) set_target_properties(${TargetBin} PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/ios/PPSSPP-Info.plist" RESOURCE "ios/Launch Screen.storyboard" + RESOURCE "ios/Settings.bundle" XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET ${DEPLOYMENT_TARGET} XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "iPhone/iPad" XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES diff --git a/ios/DisplayManager.mm b/ios/DisplayManager.mm index 1bd04aa1a1..495353a6df 100644 --- a/ios/DisplayManager.mm +++ b/ios/DisplayManager.mm @@ -49,6 +49,10 @@ } - (void)setupDisplayListener { + // Disable external display by default + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"enable_external_display"] == NO) { + return; + } if ([self listenerActive]) { NSLog(@"setupDisplayListener already called"); return; diff --git a/ios/Settings.bundle/Root.plist b/ios/Settings.bundle/Root.plist new file mode 100644 index 0000000000..627d9c50de --- /dev/null +++ b/ios/Settings.bundle/Root.plist @@ -0,0 +1,21 @@ + + + + + StringsTable + Root + PreferenceSpecifiers + + + Type + PSToggleSwitchSpecifier + Title + enable_external_display + Key + enable_external_display + DefaultValue + + + + + diff --git a/ios/Settings.bundle/en.lproj/Root.strings b/ios/Settings.bundle/en.lproj/Root.strings new file mode 100644 index 0000000000000000000000000000000000000000..cc9f6b61f9b3065f4b63eb8a342290f9c0a47a41 GIT binary patch literal 480 zcmaiwL2kk@6hvpuDXc6JDhH@oAe9?*k3!;TN71-**WNf>WFKA z(bH*I-aFWF)K+*$?N#at-fOPx`F9%6vscoI88u2LvO^$K!nb5^^$Fq}6<7zuGl$Gg z{A(Cay@P$Zp}#&ydd}%LNO_O&oHI~2{QtX`Z!*-r&0 Yfp)PX;f?n=!QRtazKXlMnP5=M^ literal 0 HcmV?d00001 From 8c282db2259397f588adf119fc0f776e0d777a38 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jun 2019 11:13:14 -0700 Subject: [PATCH 11/20] UI: Clear queued events on Event destruct. May help a crash seen in #11890. --- ext/native/ui/view.cpp | 24 +++++++++++++++++++++--- ext/native/ui/view.h | 4 +--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/ext/native/ui/view.cpp b/ext/native/ui/view.cpp index 2a0fc2cbb6..378290f951 100644 --- a/ext/native/ui/view.cpp +++ b/ext/native/ui/view.cpp @@ -60,9 +60,22 @@ void DispatchEvents() { } void RemoveQueuedEvents(View *v) { - for (size_t i = 0; i < g_dispatchQueue.size(); i++) { - if (g_dispatchQueue[i].params.v == v) - g_dispatchQueue.erase(g_dispatchQueue.begin() + i); + for (auto it = g_dispatchQueue.begin(); it != g_dispatchQueue.end(); ) { + if (it->params.v == v) { + it = g_dispatchQueue.erase(it); + } else { + ++it; + } + } +} + +void RemoveQueuedEvents(Event *e) { + for (auto it = g_dispatchQueue.begin(); it != g_dispatchQueue.end(); ) { + if (it->e == e) { + it = g_dispatchQueue.erase(it); + } else { + ++it; + } } } @@ -157,6 +170,11 @@ EventReturn Event::Dispatch(EventParams &e) { return UI::EVENT_SKIPPED; } +Event::~Event() { + handlers_.clear(); + RemoveQueuedEvents(this); +} + View::~View() { if (HasFocus()) SetFocusedView(0); diff --git a/ext/native/ui/view.h b/ext/native/ui/view.h index 45d0538d44..c59110c0d2 100644 --- a/ext/native/ui/view.h +++ b/ext/native/ui/view.h @@ -240,9 +240,7 @@ struct HandlerRegistration { class Event { public: Event() {} - ~Event() { - handlers_.clear(); - } + ~Event(); // Call this from input thread or whatever, it doesn't matter void Trigger(EventParams &e); // Call this from UI thread From eaba867f28752e55ad2d1b2b29f456f7a9dc39fb Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jun 2019 11:25:27 -0700 Subject: [PATCH 12/20] SaveState: Fail load on decompression error. Simply checking the size isn't enough, because it doesn't write the decompressed size in the case of invalid data. May help the crash in #11890. --- Common/ChunkFile.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Common/ChunkFile.cpp b/Common/ChunkFile.cpp index 4e2334e735..22327a7575 100644 --- a/Common/ChunkFile.cpp +++ b/Common/ChunkFile.cpp @@ -249,19 +249,27 @@ CChunkFileReader::Error CChunkFileReader::LoadFile(const std::string &filename, return ERROR_BAD_FILE; } - _buffer = buffer; if (header.Compress) { u8 *uncomp_buffer = new u8[header.UncompressedSize]; size_t uncomp_size = header.UncompressedSize; - snappy_uncompress((const char *)buffer, sz, (char *)uncomp_buffer, &uncomp_size); + auto status = snappy_uncompress((const char *)buffer, sz, (char *)uncomp_buffer, &uncomp_size); + if (status != SNAPPY_OK) { + ERROR_LOG(SAVESTATE, "ChunkReader: Failed to decompress file"); + delete [] uncomp_buffer; + delete [] buffer; + return ERROR_BAD_FILE; + } if ((u32)uncomp_size != header.UncompressedSize) { ERROR_LOG(SAVESTATE, "Size mismatch: file: %u calc: %u", header.UncompressedSize, (u32)uncomp_size); delete [] uncomp_buffer; + delete [] buffer; return ERROR_BAD_FILE; } _buffer = uncomp_buffer; sz = uncomp_size; delete [] buffer; + } else { + _buffer = buffer; } if (header.GitVersion[31]) { From 3de796779cd84a55c651faef04e410ebe5d86704 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jun 2019 15:30:30 -0700 Subject: [PATCH 13/20] Debugger: Fix subtexture readback crash. When the texture image is scaled, we don't properly know its size, so we were allocating too small of a buffer. Since this is only used for debugging, let's just get the actual texture size. --- ext/native/thin3d/GLQueueRunner.cpp | 57 +++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/ext/native/thin3d/GLQueueRunner.cpp b/ext/native/thin3d/GLQueueRunner.cpp index cf4dd780dd..5fd98a49f6 100644 --- a/ext/native/thin3d/GLQueueRunner.cpp +++ b/ext/native/thin3d/GLQueueRunner.cpp @@ -1,3 +1,4 @@ +#include #include "Common/MemoryUtil.h" #include "Core/Reporting.h" #include "GLQueueRunner.h" @@ -1318,25 +1319,51 @@ void GLQueueRunner::PerformReadback(const GLRStep &pass) { void GLQueueRunner::PerformReadbackImage(const GLRStep &pass) { GLRTexture *tex = pass.readback_image.texture; - glBindTexture(GL_TEXTURE_2D, tex->texture); - - CHECK_GL_ERROR_IF_DEBUG(); - #ifndef USING_GLES2 - int pixelStride = pass.readback_image.srcRect.w; - glPixelStorei(GL_PACK_ALIGNMENT, 4); - GLRect2D rect = pass.readback_image.srcRect; - int size = 4 * rect.w * rect.h; - if (size > readbackBufferSize_) { - delete[] readbackBuffer_; - readbackBuffer_ = new uint8_t[size]; - readbackBufferSize_ = size; - } + if (gl_extensions.VersionGEThan(4, 5)) { + int size = 4 * rect.w * rect.h; + if (size > readbackBufferSize_) { + delete[] readbackBuffer_; + readbackBuffer_ = new uint8_t[size]; + readbackBufferSize_ = size; + } - glPixelStorei(GL_PACK_ALIGNMENT, 4); - glGetTexImage(GL_TEXTURE_2D, pass.readback_image.mipLevel, GL_RGBA, GL_UNSIGNED_BYTE, readbackBuffer_); + glPixelStorei(GL_PACK_ALIGNMENT, 4); + glGetTextureSubImage(tex->texture, pass.readback_image.mipLevel, rect.x, rect.y, 0, rect.w, rect.h, 1, GL_RGBA, GL_UNSIGNED_BYTE, readbackBufferSize_, readbackBuffer_); + } else { + glBindTexture(GL_TEXTURE_2D, tex->texture); + + CHECK_GL_ERROR_IF_DEBUG(); + + GLint w, h; + // This is only used for debugging (currently), and GL doesn't support a subrectangle. + glGetTexLevelParameteriv(GL_TEXTURE_2D, pass.readback_image.mipLevel, GL_TEXTURE_WIDTH, &w); + glGetTexLevelParameteriv(GL_TEXTURE_2D, pass.readback_image.mipLevel, GL_TEXTURE_HEIGHT, &h); + + int size = 4 * std::max((int)w, rect.x + rect.w) * std::max((int)h, rect.h); + if (size > readbackBufferSize_) { + delete[] readbackBuffer_; + readbackBuffer_ = new uint8_t[size]; + readbackBufferSize_ = size; + } + + glPixelStorei(GL_PACK_ALIGNMENT, 4); + glPixelStorei(GL_PACK_ROW_LENGTH, rect.x + rect.w); + glGetTexImage(GL_TEXTURE_2D, pass.readback_image.mipLevel, GL_RGBA, GL_UNSIGNED_BYTE, readbackBuffer_); + glPixelStorei(GL_PACK_ROW_LENGTH, 0); + + if (rect.x != 0 || rect.y != 0) { + int dstStride = 4 * rect.w; + int srcStride = 4 * (rect.x + rect.w); + int xoff = 4 * rect.x; + int yoff = rect.y * srcStride; + for (int y = 0; y < rect.h; ++y) { + memmove(readbackBuffer_ + h * dstStride, readbackBuffer_ + yoff + h * srcStride + xoff, dstStride); + } + } + } #endif CHECK_GL_ERROR_IF_DEBUG(); From 7b8d3e0c36458101d991abb6f40d1e8b5dc6d26a Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jun 2019 16:14:18 -0700 Subject: [PATCH 14/20] GPU: Improve a 5551 stencil emulation case. Hammerin' Hero (see #10777) uses ALWAYS with INCR, which can be rewritten to maintain more correct stencil values. --- GPU/Common/GPUStateUtils.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/GPU/Common/GPUStateUtils.cpp b/GPU/Common/GPUStateUtils.cpp index f16f69cd7e..58fcfee419 100644 --- a/GPU/Common/GPUStateUtils.cpp +++ b/GPU/Common/GPUStateUtils.cpp @@ -1246,6 +1246,10 @@ static void ConvertStencilFunc5551(GenericStencilFuncState &state) { state.testRef = usedRef; // Nuke the mask as well, since this is always/never, just for consistency. state.testMask = 0xFF; + } else { + // Not used, so let's make the ref 0xFF which is a useful value later. + state.testRef = 0xFF; + state.testMask = 0xFF; } }; @@ -1341,6 +1345,10 @@ static void ConvertStencilFunc5551(GenericStencilFuncState &state) { // Otherwise we get 1, which we mostly handle, but won't INVERT correctly. rewriteOps(GE_STENCILOP_INCR, GE_STENCILOP_INVERT); } + if (!usesRef && state.testRef == 0xFF) { + // Safe to use REPLACE instead of INCR. + rewriteOps(GE_STENCILOP_INCR, GE_STENCILOP_REPLACE); + } } void ConvertStencilFuncState(GenericStencilFuncState &state) { From 8cd5a1bd93e9baa67f20b3eaa7950996be4dd290 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jun 2019 16:47:44 -0700 Subject: [PATCH 15/20] GLES: Update proj matrix on viewport change. This is necessary because, for large (or negative) viewports, we adjust it to account for what GL cannot do. Fixes #12115. --- GPU/GPUCommon.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 4ad5ddfbf6..1af5afd5c2 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -188,10 +188,10 @@ const CommonCommandTableEntry commonCommandTable[] = { // Viewport. { GE_CMD_OFFSETX, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, { GE_CMD_OFFSETY, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE }, - { GE_CMD_VIEWPORTXSCALE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_VIEWPORTSCISSOR_STATE }, - { GE_CMD_VIEWPORTYSCALE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_VIEWPORTSCISSOR_STATE }, - { GE_CMD_VIEWPORTXCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_VIEWPORTSCISSOR_STATE }, - { GE_CMD_VIEWPORTYCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_VIEWPORTSCISSOR_STATE }, + { GE_CMD_VIEWPORTXSCALE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE }, + { GE_CMD_VIEWPORTYSCALE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE }, + { GE_CMD_VIEWPORTXCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE }, + { GE_CMD_VIEWPORTYCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE }, { GE_CMD_VIEWPORTZSCALE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_DEPTHRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE }, { GE_CMD_VIEWPORTZCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_DEPTHRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE }, { GE_CMD_DEPTHCLAMPENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE | DIRTY_RASTER_STATE }, From b37553fba25c0ca543127067141be65db4c5b879 Mon Sep 17 00:00:00 2001 From: Xie Yi Date: Mon, 24 Jun 2019 08:49:47 +0800 Subject: [PATCH 16/20] fix indent --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 547d322307..558fa7b90c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1988,7 +1988,7 @@ if(TargetBin) endif() if(IOS) - list(APPEND NativeAssets ios/Settings.bundle) + list(APPEND NativeAssets ios/Settings.bundle) add_executable(${TargetBin} MACOSX_BUNDLE ${ICON_PATH_ABS} ${NativeAssets} ${SHADER_FILES} ${FLASH0_FILES} ${LANG_FILES} ${NativeAppSource} "ios/Launch Screen.storyboard") else() add_executable(${TargetBin} MACOSX_BUNDLE ${ICON_PATH_ABS} ${NativeAssets} ${SHADER_FILES} ${FLASH0_FILES} ${LANG_FILES} ${NativeAppSource}) From 9e98642258e2713c9f2853166c8e87cd8119bac0 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jun 2019 18:29:36 -0700 Subject: [PATCH 17/20] GE Debugger: Cache decompressed frame commands. This adds to noise in profiling the frame dump. --- GPU/Debugger/Record.cpp | 229 ++++++++++++++++++++++------------------ 1 file changed, 128 insertions(+), 101 deletions(-) diff --git a/GPU/Debugger/Record.cpp b/GPU/Debugger/Record.cpp index fe25ad1ba6..95e2df1466 100644 --- a/GPU/Debugger/Record.cpp +++ b/GPU/Debugger/Record.cpp @@ -22,6 +22,7 @@ #include #include #include "base/stringutil.h" +#include "profiler/profiler.h" #include "Common/Common.h" #include "Common/FileUtil.h" #include "Common/Log.h" @@ -102,40 +103,6 @@ static std::vector lastRegisters; static std::vector lastTextures; static std::set lastRenderTargets; -// TODO: Maybe move execute to another file? -class DumpExecute { -public: - ~DumpExecute(); - - bool Run(); - -private: - void SyncStall(); - bool SubmitCmds(void *p, u32 sz); - void SubmitListEnd(); - - void Init(u32 ptr, u32 sz); - void Registers(u32 ptr, u32 sz); - void Vertices(u32 ptr, u32 sz); - void Indices(u32 ptr, u32 sz); - void Clut(u32 ptr, u32 sz); - void TransferSrc(u32 ptr, u32 sz); - void Memset(u32 ptr, u32 sz); - void MemcpyDest(u32 ptr, u32 sz); - void Memcpy(u32 ptr, u32 sz); - void Texture(int level, u32 ptr, u32 sz); - void Framebuf(int level, u32 ptr, u32 sz); - void Display(u32 ptr, u32 sz); - - u32 execMemcpyDest = 0; - u32 execListBuf = 0; - u32 execListPos = 0; - u32 execListID = 0; - const int LIST_BUF_SIZE = 256 * 1024; - std::vector execListQueue; - u16 lastBufw_[8]{}; -}; - // This class maps pushbuffer (dump data) sections to PSP memory. // Dumps can be larger than available PSP memory, because they include generated data too. // @@ -144,6 +111,9 @@ private: // Slabs are managed with LRU, extra buffers are round-robin. class BufMapping { public: + BufMapping(const std::vector &pushbuf) : pushbuf_(pushbuf) { + } + // Returns a pointer to contiguous memory for this access, or else 0 (failure). u32 Map(u32 bufpos, u32 sz, const std::function &flush); @@ -178,9 +148,9 @@ protected: // An aligned large mapping of the pushbuffer in PSP RAM. struct SlabInfo { - u32 psp_pointer_; - u32 buf_pointer_; - int last_used_; + u32 psp_pointer_ = 0; + u32 buf_pointer_ = 0; + int last_used_ = 0; bool Matches(u32 bufpos) { // We check psp_pointer_ because bufpos = 0 is valid, and the initial value. @@ -202,15 +172,15 @@ protected: bool Alloc(); void Free(); - bool Setup(u32 bufpos); + bool Setup(u32 bufpos, const std::vector &pushbuf_); }; // An adhoc mapping of the pushbuffer (either larger than a slab or straddling slabs.) // Remember: texture data, verts, etc. must be contiguous. struct ExtraInfo { - u32 psp_pointer_; - u32 buf_pointer_; - u32 size_; + u32 psp_pointer_ = 0; + u32 buf_pointer_ = 0; + u32 size_ = 0; bool Matches(u32 bufpos, u32 sz) { // We check psp_pointer_ because bufpos = 0 is valid, and the initial value. @@ -221,16 +191,16 @@ protected: return psp_pointer_; } - bool Alloc(u32 bufpos, u32 sz); + bool Alloc(u32 bufpos, u32 sz, const std::vector &pushbuf_); void Free(); }; - SlabInfo slabs_[SLAB_COUNT]; + SlabInfo slabs_[SLAB_COUNT]{}; u32 extraOffset_ = 0; - ExtraInfo extra_[EXTRA_COUNT]; -}; + ExtraInfo extra_[EXTRA_COUNT]{}; -static BufMapping execMapping; + const std::vector &pushbuf_; +}; u32 BufMapping::Map(u32 bufpos, u32 sz, const std::function &flush) { int slab1 = bufpos / SLAB_SIZE; @@ -263,7 +233,7 @@ u32 BufMapping::MapSlab(u32 bufpos, const std::function &flush) { flush(); // Okay, we need to allocate. - if (!slabs_[best].Setup(slab_pos)) { + if (!slabs_[best].Setup(slab_pos, pushbuf_)) { return 0; } return slabs_[best].Ptr(bufpos); @@ -283,12 +253,12 @@ u32 BufMapping::MapExtra(u32 bufpos, u32 sz, const std::function &flush) int i = extraOffset_; extraOffset_ = (extraOffset_ + 1) % EXTRA_COUNT; - if (!extra_[i].Alloc(bufpos, sz)) { + if (!extra_[i].Alloc(bufpos, sz, pushbuf_)) { // Let's try to power on - hopefully none of these are still in use. for (int i = 0; i < EXTRA_COUNT; ++i) { extra_[i].Free(); } - if (!extra_[i].Alloc(bufpos, sz)) { + if (!extra_[i].Alloc(bufpos, sz, pushbuf_)) { return 0; } } @@ -313,7 +283,7 @@ void BufMapping::SlabInfo::Free() { } } -bool BufMapping::ExtraInfo::Alloc(u32 bufpos, u32 sz) { +bool BufMapping::ExtraInfo::Alloc(u32 bufpos, u32 sz, const std::vector &pushbuf_) { // Make sure we've freed any previous allocation first. Free(); @@ -328,7 +298,7 @@ bool BufMapping::ExtraInfo::Alloc(u32 bufpos, u32 sz) { buf_pointer_ = bufpos; size_ = sz; - Memory::MemcpyUnchecked(psp_pointer_, pushbuf.data() + bufpos, sz); + Memory::MemcpyUnchecked(psp_pointer_, pushbuf_.data() + bufpos, sz); return true; } @@ -340,7 +310,7 @@ void BufMapping::ExtraInfo::Free() { } } -bool BufMapping::SlabInfo::Setup(u32 bufpos) { +bool BufMapping::SlabInfo::Setup(u32 bufpos, const std::vector &pushbuf_) { // If it already has RAM, we're simply taking it over. Slabs come only in one size. if (psp_pointer_ == 0) { if (!Alloc()) { @@ -349,8 +319,8 @@ bool BufMapping::SlabInfo::Setup(u32 bufpos) { } buf_pointer_ = bufpos; - u32 sz = std::min((u32)SLAB_SIZE, (u32)pushbuf.size() - bufpos); - Memory::MemcpyUnchecked(psp_pointer_, pushbuf.data() + bufpos, sz); + u32 sz = std::min((u32)SLAB_SIZE, (u32)pushbuf_.size() - bufpos); + Memory::MemcpyUnchecked(psp_pointer_, pushbuf_.data() + bufpos, sz); slabGeneration_++; last_used_ = slabGeneration_; @@ -359,6 +329,47 @@ bool BufMapping::SlabInfo::Setup(u32 bufpos) { int BufMapping::slabGeneration_ = 0; +// TODO: Maybe move execute to another file? +class DumpExecute { +public: + DumpExecute(const std::vector &pushbuf, const std::vector &commands) + : pushbuf_(pushbuf), commands_(commands), mapping_(pushbuf) { + } + ~DumpExecute(); + + bool Run(); + +private: + void SyncStall(); + bool SubmitCmds(const void *p, u32 sz); + void SubmitListEnd(); + + void Init(u32 ptr, u32 sz); + void Registers(u32 ptr, u32 sz); + void Vertices(u32 ptr, u32 sz); + void Indices(u32 ptr, u32 sz); + void Clut(u32 ptr, u32 sz); + void TransferSrc(u32 ptr, u32 sz); + void Memset(u32 ptr, u32 sz); + void MemcpyDest(u32 ptr, u32 sz); + void Memcpy(u32 ptr, u32 sz); + void Texture(int level, u32 ptr, u32 sz); + void Framebuf(int level, u32 ptr, u32 sz); + void Display(u32 ptr, u32 sz); + + u32 execMemcpyDest = 0; + u32 execListBuf = 0; + u32 execListPos = 0; + u32 execListID = 0; + const int LIST_BUF_SIZE = 256 * 1024; + std::vector execListQueue; + u16 lastBufw_[8]{}; + + const std::vector &pushbuf_; + const std::vector &commands_; + BufMapping mapping_; +}; + static void FlushRegisters() { if (!lastRegisters.empty()) { Command last{CommandType::REGISTERS}; @@ -882,7 +893,7 @@ void DumpExecute::SyncStall() { CoreTiming::ForceCheck(); } -bool DumpExecute::SubmitCmds(void *p, u32 sz) { +bool DumpExecute::SubmitCmds(const void *p, u32 sz) { if (execListBuf == 0) { u32 allocSize = LIST_BUF_SIZE; execListBuf = userMemory.Alloc(allocSize, "List buf"); @@ -967,16 +978,16 @@ void DumpExecute::SubmitListEnd() { } void DumpExecute::Init(u32 ptr, u32 sz) { - gstate.Restore((u32_le *)(pushbuf.data() + ptr)); + gstate.Restore((u32_le *)(pushbuf_.data() + ptr)); gpu->ReapplyGfxState(); } void DumpExecute::Registers(u32 ptr, u32 sz) { - SubmitCmds(pushbuf.data() + ptr, sz); + SubmitCmds(pushbuf_.data() + ptr, sz); } void DumpExecute::Vertices(u32 ptr, u32 sz) { - u32 psp = execMapping.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); + u32 psp = mapping_.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); if (psp == 0) { ERROR_LOG(SYSTEM, "Unable to allocate for vertices"); return; @@ -987,7 +998,7 @@ void DumpExecute::Vertices(u32 ptr, u32 sz) { } void DumpExecute::Indices(u32 ptr, u32 sz) { - u32 psp = execMapping.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); + u32 psp = mapping_.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); if (psp == 0) { ERROR_LOG(SYSTEM, "Unable to allocate for indices"); return; @@ -998,7 +1009,7 @@ void DumpExecute::Indices(u32 ptr, u32 sz) { } void DumpExecute::Clut(u32 ptr, u32 sz) { - u32 psp = execMapping.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); + u32 psp = mapping_.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); if (psp == 0) { ERROR_LOG(SYSTEM, "Unable to allocate for clut"); return; @@ -1009,7 +1020,7 @@ void DumpExecute::Clut(u32 ptr, u32 sz) { } void DumpExecute::TransferSrc(u32 ptr, u32 sz) { - u32 psp = execMapping.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); + u32 psp = mapping_.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); if (psp == 0) { ERROR_LOG(SYSTEM, "Unable to allocate for transfer"); return; @@ -1023,13 +1034,14 @@ void DumpExecute::TransferSrc(u32 ptr, u32 sz) { } void DumpExecute::Memset(u32 ptr, u32 sz) { + PROFILE_THIS_SCOPE("ReplayMemset"); struct MemsetCommand { u32 dest; int value; u32 sz; }; - const MemsetCommand *data = (const MemsetCommand *)(pushbuf.data() + ptr); + const MemsetCommand *data = (const MemsetCommand *)(pushbuf_.data() + ptr); if (Memory::IsVRAMAddress(data->dest)) { SyncStall(); @@ -1038,19 +1050,20 @@ void DumpExecute::Memset(u32 ptr, u32 sz) { } void DumpExecute::MemcpyDest(u32 ptr, u32 sz) { - execMemcpyDest = *(const u32 *)(pushbuf.data() + ptr); + execMemcpyDest = *(const u32 *)(pushbuf_.data() + ptr); } void DumpExecute::Memcpy(u32 ptr, u32 sz) { + PROFILE_THIS_SCOPE("ReplayMemcpy"); if (Memory::IsVRAMAddress(execMemcpyDest)) { SyncStall(); - Memory::MemcpyUnchecked(execMemcpyDest, pushbuf.data() + ptr, sz); + Memory::MemcpyUnchecked(execMemcpyDest, pushbuf_.data() + ptr, sz); gpu->PerformMemoryUpload(execMemcpyDest, sz); } } void DumpExecute::Texture(int level, u32 ptr, u32 sz) { - u32 psp = execMapping.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); + u32 psp = mapping_.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); if (psp == 0) { ERROR_LOG(SYSTEM, "Unable to allocate for texture"); return; @@ -1063,6 +1076,7 @@ void DumpExecute::Texture(int level, u32 ptr, u32 sz) { } void DumpExecute::Framebuf(int level, u32 ptr, u32 sz) { + PROFILE_THIS_SCOPE("ReplayFramebuf"); struct FramebufData { u32 addr; int bufw; @@ -1070,7 +1084,7 @@ void DumpExecute::Framebuf(int level, u32 ptr, u32 sz) { u32 pad; }; - FramebufData *framebuf = (FramebufData *)(pushbuf.data() + ptr); + FramebufData *framebuf = (FramebufData *)(pushbuf_.data() + ptr); u32 bufwCmd = GE_CMD_TEXBUFWIDTH0 + level; u32 addrCmd = GE_CMD_TEXADDR0 + level; @@ -1085,7 +1099,7 @@ void DumpExecute::Framebuf(int level, u32 ptr, u32 sz) { // Could potentially always skip if !isTarget, but playing it safe for offset texture behavior. if (Memory::IsValidRange(framebuf->addr, pspSize) && (!isTarget || !g_Config.bSoftwareRendering)) { // Intentionally don't trigger an upload here. - Memory::MemcpyUnchecked(framebuf->addr, pushbuf.data() + ptr + headerSize, pspSize); + Memory::MemcpyUnchecked(framebuf->addr, pushbuf_.data() + ptr + headerSize, pspSize); } } @@ -1095,7 +1109,7 @@ void DumpExecute::Display(u32 ptr, u32 sz) { int linesize, pixelFormat; }; - DisplayBufData *disp = (DisplayBufData *)(pushbuf.data() + ptr); + DisplayBufData *disp = (DisplayBufData *)(pushbuf_.data() + ptr); // Sync up drawing. SyncStall(); @@ -1111,14 +1125,11 @@ DumpExecute::~DumpExecute() { execListBuf = 0; } execListPos = 0; - execMapping.Reset(); - - commands.clear(); - pushbuf.clear(); + mapping_.Reset(); } bool DumpExecute::Run() { - for (const Command &cmd : commands) { + for (const Command &cmd : commands_) { switch (cmd.type) { case CommandType::INIT: Init(cmd.ptr, cmd.sz); @@ -1211,41 +1222,57 @@ static bool ReadCompressed(u32 fp, void *dest, size_t sz) { return real_size == sz; } +static std::string lastExecFilename; +static std::vector lastExecCommands; +static std::vector lastExecPushbuf; + +static void ReplayStop() { + lastExecFilename.clear(); + lastExecCommands.clear(); + lastExecPushbuf.clear(); +} + bool RunMountedReplay(const std::string &filename) { _assert_msg_(SYSTEM, !active && !nextFrame, "Cannot run replay while recording."); - u32 fp = pspFileSystem.OpenFile(filename, FILEACCESS_READ); - u8 header[8]{}; - int version = 0; - pspFileSystem.ReadFile(fp, header, sizeof(header)); - pspFileSystem.ReadFile(fp, (u8 *)&version, sizeof(version)); + Core_ListenStopRequest(&ReplayStop); + if (lastExecFilename != filename) { + PROFILE_THIS_SCOPE("ReplayLoad"); + u32 fp = pspFileSystem.OpenFile(filename, FILEACCESS_READ); + u8 header[8]{}; + int version = 0; + pspFileSystem.ReadFile(fp, header, sizeof(header)); + pspFileSystem.ReadFile(fp, (u8 *)&version, sizeof(version)); + + if (memcmp(header, HEADER, sizeof(header)) != 0 || version > VERSION || version < MIN_VERSION) { + ERROR_LOG(SYSTEM, "Invalid GE dump or unsupported version"); + pspFileSystem.CloseFile(fp); + return false; + } + + u32 sz = 0; + pspFileSystem.ReadFile(fp, (u8 *)&sz, sizeof(sz)); + u32 bufsz = 0; + pspFileSystem.ReadFile(fp, (u8 *)&bufsz, sizeof(bufsz)); + + lastExecCommands.resize(sz); + lastExecPushbuf.resize(bufsz); + + bool truncated = false; + truncated = truncated || !ReadCompressed(fp, lastExecCommands.data(), sizeof(Command) * sz); + truncated = truncated || !ReadCompressed(fp, lastExecPushbuf.data(), bufsz); - if (memcmp(header, HEADER, sizeof(header)) != 0 || version > VERSION || version < MIN_VERSION) { - ERROR_LOG(SYSTEM, "Invalid GE dump or unsupported version"); pspFileSystem.CloseFile(fp); - return false; + + if (truncated) { + ERROR_LOG(SYSTEM, "Truncated GE dump"); + return false; + } + + lastExecFilename = filename; } - u32 sz = 0; - pspFileSystem.ReadFile(fp, (u8 *)&sz, sizeof(sz)); - u32 bufsz = 0; - pspFileSystem.ReadFile(fp, (u8 *)&bufsz, sizeof(bufsz)); - - commands.resize(sz); - pushbuf.resize(bufsz); - - bool truncated = false; - truncated = truncated || !ReadCompressed(fp, commands.data(), sizeof(Command) * sz); - truncated = truncated || !ReadCompressed(fp, pushbuf.data(), bufsz); - - pspFileSystem.CloseFile(fp); - - if (truncated) { - ERROR_LOG(SYSTEM, "Truncated GE dump"); - return false; - } - - DumpExecute executor; + DumpExecute executor(lastExecPushbuf, lastExecCommands); return executor.Run(); } From 93069756e6c8c9c0b2eb9417c0e0231ef3cfe7fc Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 23 Jun 2019 18:48:40 -0700 Subject: [PATCH 18/20] GE Debugger: Split playback into a separate file. --- CMakeLists.txt | 3 + Core/HLE/sceKernelModule.cpp | 2 +- GPU/Debugger/Playback.cpp | 706 +++++++++++++++++++++++++++ GPU/Debugger/Playback.h | 26 + GPU/Debugger/Record.cpp | 718 +--------------------------- GPU/Debugger/Record.h | 2 - GPU/Debugger/RecordFormat.h | 72 +++ GPU/GPU.vcxproj | 3 + GPU/GPU.vcxproj.filters | 9 + UWP/GPU_UWP/GPU_UWP.vcxproj | 3 + UWP/GPU_UWP/GPU_UWP.vcxproj.filters | 3 + android/jni/Android.mk | 1 + libretro/Makefile.common | 1 + 13 files changed, 829 insertions(+), 720 deletions(-) create mode 100644 GPU/Debugger/Playback.cpp create mode 100644 GPU/Debugger/Playback.h create mode 100644 GPU/Debugger/RecordFormat.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 89ec8303c5..4c594741ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1379,8 +1379,11 @@ set(GPU_SOURCES GPU/Debugger/Breakpoints.h GPU/Debugger/Debugger.cpp GPU/Debugger/Debugger.h + GPU/Debugger/Playback.cpp + GPU/Debugger/Playback.h GPU/Debugger/Record.cpp GPU/Debugger/Record.h + GPU/Debugger/RecordFormat.h GPU/Debugger/Stepping.cpp GPU/Debugger/Stepping.h GPU/GPUInterface.h diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index 36241fece3..8116388cf6 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -58,7 +58,7 @@ #include "Core/HLE/KernelWaitHelpers.h" #include "Core/ELF/ParamSFO.h" -#include "GPU/Debugger/Record.h" +#include "GPU/Debugger/Playback.h" #include "GPU/GPU.h" #include "GPU/GPUInterface.h" #include "GPU/GPUState.h" diff --git a/GPU/Debugger/Playback.cpp b/GPU/Debugger/Playback.cpp new file mode 100644 index 0000000000..806d43db9f --- /dev/null +++ b/GPU/Debugger/Playback.cpp @@ -0,0 +1,706 @@ +// Copyright (c) 2017- 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 +#include +#include +#include +#include +#include "profiler/profiler.h" +#include "Common/Common.h" +#include "Common/Log.h" +#include "Core/Core.h" +#include "Core/CoreTiming.h" +#include "Core/FileSystems/MetaFileSystem.h" +#include "Core/HLE/sceDisplay.h" +#include "Core/HLE/sceKernelMemory.h" +#include "Core/MemMap.h" +#include "Core/MIPS/MIPS.h" +#include "Core/System.h" +#include "GPU/GPUInterface.h" +#include "GPU/GPUState.h" +#include "GPU/ge_constants.h" +#include "GPU/Debugger/Playback.h" +#include "GPU/Debugger/Record.h" +#include "GPU/Debugger/RecordFormat.h" + +namespace GPURecord { + +static std::string lastExecFilename; +static std::vector lastExecCommands; +static std::vector lastExecPushbuf; + +// This class maps pushbuffer (dump data) sections to PSP memory. +// Dumps can be larger than available PSP memory, because they include generated data too. +// +// If possible, it maps to dynamically allocated "slabs" so nearby access is fast. +// Otherwise it uses "extra" allocations to manage sections that straddle two slabs. +// Slabs are managed with LRU, extra buffers are round-robin. +class BufMapping { +public: + BufMapping(const std::vector &pushbuf) : pushbuf_(pushbuf) { + } + + // Returns a pointer to contiguous memory for this access, or else 0 (failure). + u32 Map(u32 bufpos, u32 sz, const std::function &flush); + + // Clear and reset allocations made. + void Reset() { + slabGeneration_ = 0; + extraOffset_ = 0; + for (int i = 0; i < SLAB_COUNT; ++i) { + slabs_[i].Free(); + } + for (int i = 0; i < EXTRA_COUNT; ++i) { + extra_[i].Free(); + } + } + +protected: + u32 MapSlab(u32 bufpos, const std::function &flush); + u32 MapExtra(u32 bufpos, u32 sz, const std::function &flush); + + enum { + // These numbers kept low because we only have 24 MB of user memory to map into. + SLAB_SIZE = 1 * 1024 * 1024, + // 10 is the number of texture units + verts + inds. + // In the worst case, we could concurrently need 10 slabs/extras at the same time. + SLAB_COUNT = 10, + EXTRA_COUNT = 10, + }; + + // The current "generation". Static simply as a convenience for access. + // This increments on every allocation, for a simple LRU. + static int slabGeneration_; + + // An aligned large mapping of the pushbuffer in PSP RAM. + struct SlabInfo { + u32 psp_pointer_ = 0; + u32 buf_pointer_ = 0; + int last_used_ = 0; + + bool Matches(u32 bufpos) { + // We check psp_pointer_ because bufpos = 0 is valid, and the initial value. + return buf_pointer_ == bufpos && psp_pointer_ != 0; + } + + // Automatically marks used for LRU purposes. + u32 Ptr(u32 bufpos) { + last_used_ = slabGeneration_; + return psp_pointer_ + (bufpos - buf_pointer_); + } + + int Age() const { + // If not allocated, it's as expired as it's gonna get. + if (psp_pointer_ == 0) + return std::numeric_limits::max(); + return slabGeneration_ - last_used_; + } + + bool Alloc(); + void Free(); + bool Setup(u32 bufpos, const std::vector &pushbuf_); + }; + + // An adhoc mapping of the pushbuffer (either larger than a slab or straddling slabs.) + // Remember: texture data, verts, etc. must be contiguous. + struct ExtraInfo { + u32 psp_pointer_ = 0; + u32 buf_pointer_ = 0; + u32 size_ = 0; + + bool Matches(u32 bufpos, u32 sz) { + // We check psp_pointer_ because bufpos = 0 is valid, and the initial value. + return buf_pointer_ == bufpos && psp_pointer_ != 0 && size_ >= sz; + } + + u32 Ptr() { + return psp_pointer_; + } + + bool Alloc(u32 bufpos, u32 sz, const std::vector &pushbuf_); + void Free(); + }; + + SlabInfo slabs_[SLAB_COUNT]{}; + u32 extraOffset_ = 0; + ExtraInfo extra_[EXTRA_COUNT]{}; + + const std::vector &pushbuf_; +}; + +u32 BufMapping::Map(u32 bufpos, u32 sz, const std::function &flush) { + int slab1 = bufpos / SLAB_SIZE; + int slab2 = (bufpos + sz - 1) / SLAB_SIZE; + + if (slab1 == slab2) { + // Doesn't straddle, so we can just map to a slab. + return MapSlab(bufpos, flush); + } else { + // We need contiguous, so we'll just allocate separately. + return MapExtra(bufpos, sz, flush); + } +} + +u32 BufMapping::MapSlab(u32 bufpos, const std::function &flush) { + u32 slab_pos = (bufpos / SLAB_SIZE) * SLAB_SIZE; + + int best = 0; + for (int i = 0; i < SLAB_COUNT; ++i) { + if (slabs_[i].Matches(slab_pos)) { + return slabs_[i].Ptr(bufpos); + } + + if (slabs_[i].Age() > slabs_[best].Age()) { + best = i; + } + } + + // Stall before mapping a new slab. + flush(); + + // Okay, we need to allocate. + if (!slabs_[best].Setup(slab_pos, pushbuf_)) { + return 0; + } + return slabs_[best].Ptr(bufpos); +} + +u32 BufMapping::MapExtra(u32 bufpos, u32 sz, const std::function &flush) { + for (int i = 0; i < EXTRA_COUNT; ++i) { + // Might be likely to reuse larger buffers straddling slabs. + if (extra_[i].Matches(bufpos, sz)) { + return extra_[i].Ptr(); + } + } + + // Stall first, so we don't stomp existing RAM. + flush(); + + int i = extraOffset_; + extraOffset_ = (extraOffset_ + 1) % EXTRA_COUNT; + + if (!extra_[i].Alloc(bufpos, sz, pushbuf_)) { + // Let's try to power on - hopefully none of these are still in use. + for (int i = 0; i < EXTRA_COUNT; ++i) { + extra_[i].Free(); + } + if (!extra_[i].Alloc(bufpos, sz, pushbuf_)) { + return 0; + } + } + return extra_[i].Ptr(); +} + +bool BufMapping::SlabInfo::Alloc() { + u32 sz = SLAB_SIZE; + psp_pointer_ = userMemory.Alloc(sz, false, "Slab"); + if (psp_pointer_ == -1) { + psp_pointer_ = 0; + } + return psp_pointer_ != 0; +} + +void BufMapping::SlabInfo::Free() { + if (psp_pointer_) { + userMemory.Free(psp_pointer_); + psp_pointer_ = 0; + buf_pointer_ = 0; + last_used_ = 0; + } +} + +bool BufMapping::ExtraInfo::Alloc(u32 bufpos, u32 sz, const std::vector &pushbuf_) { + // Make sure we've freed any previous allocation first. + Free(); + + u32 allocSize = sz; + psp_pointer_ = userMemory.Alloc(allocSize, false, "Straddle extra"); + if (psp_pointer_ == -1) { + psp_pointer_ = 0; + } + if (psp_pointer_ == 0) { + return false; + } + + buf_pointer_ = bufpos; + size_ = sz; + Memory::MemcpyUnchecked(psp_pointer_, pushbuf_.data() + bufpos, sz); + return true; +} + +void BufMapping::ExtraInfo::Free() { + if (psp_pointer_) { + userMemory.Free(psp_pointer_); + psp_pointer_ = 0; + buf_pointer_ = 0; + } +} + +bool BufMapping::SlabInfo::Setup(u32 bufpos, const std::vector &pushbuf_) { + // If it already has RAM, we're simply taking it over. Slabs come only in one size. + if (psp_pointer_ == 0) { + if (!Alloc()) { + return false; + } + } + + buf_pointer_ = bufpos; + u32 sz = std::min((u32)SLAB_SIZE, (u32)pushbuf_.size() - bufpos); + Memory::MemcpyUnchecked(psp_pointer_, pushbuf_.data() + bufpos, sz); + + slabGeneration_++; + last_used_ = slabGeneration_; + return true; +} + +int BufMapping::slabGeneration_ = 0; + +class DumpExecute { +public: + DumpExecute(const std::vector &pushbuf, const std::vector &commands) + : pushbuf_(pushbuf), commands_(commands), mapping_(pushbuf) { + } + ~DumpExecute(); + + bool Run(); + +private: + void SyncStall(); + bool SubmitCmds(const void *p, u32 sz); + void SubmitListEnd(); + + void Init(u32 ptr, u32 sz); + void Registers(u32 ptr, u32 sz); + void Vertices(u32 ptr, u32 sz); + void Indices(u32 ptr, u32 sz); + void Clut(u32 ptr, u32 sz); + void TransferSrc(u32 ptr, u32 sz); + void Memset(u32 ptr, u32 sz); + void MemcpyDest(u32 ptr, u32 sz); + void Memcpy(u32 ptr, u32 sz); + void Texture(int level, u32 ptr, u32 sz); + void Framebuf(int level, u32 ptr, u32 sz); + void Display(u32 ptr, u32 sz); + + u32 execMemcpyDest = 0; + u32 execListBuf = 0; + u32 execListPos = 0; + u32 execListID = 0; + const int LIST_BUF_SIZE = 256 * 1024; + std::vector execListQueue; + u16 lastBufw_[8]{}; + + const std::vector &pushbuf_; + const std::vector &commands_; + BufMapping mapping_; +}; + +void DumpExecute::SyncStall() { + gpu->UpdateStall(execListID, execListPos); + s64 listTicks = gpu->GetListTicks(execListID); + if (listTicks != -1) { + s64 nowTicks = CoreTiming::GetTicks(); + if (listTicks > nowTicks) { + currentMIPS->downcount -= listTicks - nowTicks; + } + } + + // Make sure downcount doesn't overflow. + CoreTiming::ForceCheck(); +} + +bool DumpExecute::SubmitCmds(const void *p, u32 sz) { + if (execListBuf == 0) { + u32 allocSize = LIST_BUF_SIZE; + execListBuf = userMemory.Alloc(allocSize, "List buf"); + if (execListBuf == -1) { + execListBuf = 0; + } + if (execListBuf == 0) { + ERROR_LOG(SYSTEM, "Unable to allocate for display list"); + return false; + } + + execListPos = execListBuf; + Memory::Write_U32(GE_CMD_NOP << 24, execListPos); + execListPos += 4; + + gpu->EnableInterrupts(false); + auto optParam = PSPPointer::Create(0); + execListID = gpu->EnqueueList(execListBuf, execListPos, -1, optParam, false); + gpu->EnableInterrupts(true); + } + + u32 pendingSize = (int)execListQueue.size() * sizeof(u32); + // Validate space for jump. + u32 allocSize = pendingSize + sz + 8; + if (execListPos + allocSize >= execListBuf + LIST_BUF_SIZE) { + Memory::Write_U32((GE_CMD_BASE << 24) | ((execListBuf >> 8) & 0x00FF0000), execListPos); + Memory::Write_U32((GE_CMD_JUMP << 24) | (execListBuf & 0x00FFFFFF), execListPos + 4); + + execListPos = execListBuf; + + // Don't continue until we've stalled. + SyncStall(); + } + + Memory::MemcpyUnchecked(execListPos, execListQueue.data(), pendingSize); + execListPos += pendingSize; + u32 writePos = execListPos; + Memory::MemcpyUnchecked(execListPos, p, sz); + execListPos += sz; + + // TODO: Unfortunate. Maybe Texture commands should contain the bufw instead. + // The goal here is to realistically combine prims in dumps. Stalling for the bufw flushes. + u32_le *ops = (u32_le *)Memory::GetPointer(writePos); + for (u32 i = 0; i < sz / 4; ++i) { + u32 cmd = ops[i] >> 24; + if (cmd >= GE_CMD_TEXBUFWIDTH0 && cmd <= GE_CMD_TEXBUFWIDTH7) { + int level = cmd - GE_CMD_TEXBUFWIDTH0; + u16 bufw = ops[i] & 0xFFFF; + + // NOP the address part of the command to avoid a flush too. + if (bufw == lastBufw_[level]) + ops[i] = GE_CMD_NOP << 24; + else + ops[i] = (gstate.texbufwidth[level] & 0xFFFF0000) | bufw; + lastBufw_[level] = bufw; + } + + // Since we're here anyway, also NOP out texture addresses. + // This makes Step Tex not hit phantom textures. + if (cmd >= GE_CMD_TEXADDR0 && cmd <= GE_CMD_TEXADDR7) { + ops[i] = GE_CMD_NOP << 24; + } + } + + execListQueue.clear(); + + return true; +} + +void DumpExecute::SubmitListEnd() { + if (execListPos == 0) { + return; + } + + // There's always space for the end, same size as a jump. + Memory::Write_U32(GE_CMD_FINISH << 24, execListPos); + Memory::Write_U32(GE_CMD_END << 24, execListPos + 4); + execListPos += 8; + + SyncStall(); + gpu->ListSync(execListID, 0); +} + +void DumpExecute::Init(u32 ptr, u32 sz) { + gstate.Restore((u32_le *)(pushbuf_.data() + ptr)); + gpu->ReapplyGfxState(); +} + +void DumpExecute::Registers(u32 ptr, u32 sz) { + SubmitCmds(pushbuf_.data() + ptr, sz); +} + +void DumpExecute::Vertices(u32 ptr, u32 sz) { + u32 psp = mapping_.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); + if (psp == 0) { + ERROR_LOG(SYSTEM, "Unable to allocate for vertices"); + return; + } + + execListQueue.push_back((GE_CMD_BASE << 24) | ((psp >> 8) & 0x00FF0000)); + execListQueue.push_back((GE_CMD_VADDR << 24) | (psp & 0x00FFFFFF)); +} + +void DumpExecute::Indices(u32 ptr, u32 sz) { + u32 psp = mapping_.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); + if (psp == 0) { + ERROR_LOG(SYSTEM, "Unable to allocate for indices"); + return; + } + + execListQueue.push_back((GE_CMD_BASE << 24) | ((psp >> 8) & 0x00FF0000)); + execListQueue.push_back((GE_CMD_IADDR << 24) | (psp & 0x00FFFFFF)); +} + +void DumpExecute::Clut(u32 ptr, u32 sz) { + u32 psp = mapping_.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); + if (psp == 0) { + ERROR_LOG(SYSTEM, "Unable to allocate for clut"); + return; + } + + execListQueue.push_back((GE_CMD_CLUTADDRUPPER << 24) | ((psp >> 8) & 0x00FF0000)); + execListQueue.push_back((GE_CMD_CLUTADDR << 24) | (psp & 0x00FFFFFF)); +} + +void DumpExecute::TransferSrc(u32 ptr, u32 sz) { + u32 psp = mapping_.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); + if (psp == 0) { + ERROR_LOG(SYSTEM, "Unable to allocate for transfer"); + return; + } + + // Need to sync in order to access gstate.transfersrcw. + SyncStall(); + + execListQueue.push_back((gstate.transfersrcw & 0xFF00FFFF) | ((psp >> 8) & 0x00FF0000)); + execListQueue.push_back(((GE_CMD_TRANSFERSRC) << 24) | (psp & 0x00FFFFFF)); +} + +void DumpExecute::Memset(u32 ptr, u32 sz) { + PROFILE_THIS_SCOPE("ReplayMemset"); + struct MemsetCommand { + u32 dest; + int value; + u32 sz; + }; + + const MemsetCommand *data = (const MemsetCommand *)(pushbuf_.data() + ptr); + + if (Memory::IsVRAMAddress(data->dest)) { + SyncStall(); + gpu->PerformMemorySet(data->dest, (u8)data->value, data->sz); + } +} + +void DumpExecute::MemcpyDest(u32 ptr, u32 sz) { + execMemcpyDest = *(const u32 *)(pushbuf_.data() + ptr); +} + +void DumpExecute::Memcpy(u32 ptr, u32 sz) { + PROFILE_THIS_SCOPE("ReplayMemcpy"); + if (Memory::IsVRAMAddress(execMemcpyDest)) { + SyncStall(); + Memory::MemcpyUnchecked(execMemcpyDest, pushbuf_.data() + ptr, sz); + gpu->PerformMemoryUpload(execMemcpyDest, sz); + } +} + +void DumpExecute::Texture(int level, u32 ptr, u32 sz) { + u32 psp = mapping_.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); + if (psp == 0) { + ERROR_LOG(SYSTEM, "Unable to allocate for texture"); + return; + } + + u32 bufwCmd = GE_CMD_TEXBUFWIDTH0 + level; + u32 addrCmd = GE_CMD_TEXADDR0 + level; + execListQueue.push_back((bufwCmd << 24) | ((psp >> 8) & 0x00FF0000) | lastBufw_[level]); + execListQueue.push_back((addrCmd << 24) | (psp & 0x00FFFFFF)); +} + +void DumpExecute::Framebuf(int level, u32 ptr, u32 sz) { + PROFILE_THIS_SCOPE("ReplayFramebuf"); + struct FramebufData { + u32 addr; + int bufw; + u32 flags; + u32 pad; + }; + + FramebufData *framebuf = (FramebufData *)(pushbuf_.data() + ptr); + + u32 bufwCmd = GE_CMD_TEXBUFWIDTH0 + level; + u32 addrCmd = GE_CMD_TEXADDR0 + level; + execListQueue.push_back((bufwCmd << 24) | ((framebuf->addr >> 8) & 0x00FF0000) | framebuf->bufw); + execListQueue.push_back((addrCmd << 24) | (framebuf->addr & 0x00FFFFFF)); + lastBufw_[level] = framebuf->bufw; + + // And now also copy the data into VRAM (in case it wasn't actually rendered.) + u32 headerSize = (u32)sizeof(FramebufData); + u32 pspSize = sz - headerSize; + const bool isTarget = (framebuf->flags & 1) != 0; + // Could potentially always skip if !isTarget, but playing it safe for offset texture behavior. + if (Memory::IsValidRange(framebuf->addr, pspSize) && (!isTarget || !g_Config.bSoftwareRendering)) { + // Intentionally don't trigger an upload here. + Memory::MemcpyUnchecked(framebuf->addr, pushbuf_.data() + ptr + headerSize, pspSize); + } +} + +void DumpExecute::Display(u32 ptr, u32 sz) { + struct DisplayBufData { + PSPPointer topaddr; + int linesize, pixelFormat; + }; + + DisplayBufData *disp = (DisplayBufData *)(pushbuf_.data() + ptr); + + // Sync up drawing. + SyncStall(); + + __DisplaySetFramebuf(disp->topaddr.ptr, disp->linesize, disp->pixelFormat, 1); + __DisplaySetFramebuf(disp->topaddr.ptr, disp->linesize, disp->pixelFormat, 0); +} + +DumpExecute::~DumpExecute() { + execMemcpyDest = 0; + if (execListBuf) { + userMemory.Free(execListBuf); + execListBuf = 0; + } + execListPos = 0; + mapping_.Reset(); +} + +bool DumpExecute::Run() { + for (const Command &cmd : commands_) { + switch (cmd.type) { + case CommandType::INIT: + Init(cmd.ptr, cmd.sz); + break; + + case CommandType::REGISTERS: + Registers(cmd.ptr, cmd.sz); + break; + + case CommandType::VERTICES: + Vertices(cmd.ptr, cmd.sz); + break; + + case CommandType::INDICES: + Indices(cmd.ptr, cmd.sz); + break; + + case CommandType::CLUT: + Clut(cmd.ptr, cmd.sz); + break; + + case CommandType::TRANSFERSRC: + TransferSrc(cmd.ptr, cmd.sz); + break; + + case CommandType::MEMSET: + Memset(cmd.ptr, cmd.sz); + break; + + case CommandType::MEMCPYDEST: + MemcpyDest(cmd.ptr, cmd.sz); + break; + + case CommandType::MEMCPYDATA: + Memcpy(cmd.ptr, cmd.sz); + break; + + case CommandType::TEXTURE0: + case CommandType::TEXTURE1: + case CommandType::TEXTURE2: + case CommandType::TEXTURE3: + case CommandType::TEXTURE4: + case CommandType::TEXTURE5: + case CommandType::TEXTURE6: + case CommandType::TEXTURE7: + Texture((int)cmd.type - (int)CommandType::TEXTURE0, cmd.ptr, cmd.sz); + break; + + case CommandType::FRAMEBUF0: + case CommandType::FRAMEBUF1: + case CommandType::FRAMEBUF2: + case CommandType::FRAMEBUF3: + case CommandType::FRAMEBUF4: + case CommandType::FRAMEBUF5: + case CommandType::FRAMEBUF6: + case CommandType::FRAMEBUF7: + Framebuf((int)cmd.type - (int)CommandType::FRAMEBUF0, cmd.ptr, cmd.sz); + break; + + case CommandType::DISPLAY: + Display(cmd.ptr, cmd.sz); + break; + + default: + ERROR_LOG(SYSTEM, "Unsupported GE dump command: %d", (int)cmd.type); + return false; + } + } + + SubmitListEnd(); + return true; +} + +static bool ReadCompressed(u32 fp, void *dest, size_t sz) { + u32 compressed_size = 0; + if (pspFileSystem.ReadFile(fp, (u8 *)&compressed_size, sizeof(compressed_size)) != sizeof(compressed_size)) { + return false; + } + + u8 *compressed = new u8[compressed_size]; + if (pspFileSystem.ReadFile(fp, compressed, compressed_size) != compressed_size) { + delete[] compressed; + return false; + } + + size_t real_size = sz; + snappy_uncompress((const char *)compressed, compressed_size, (char *)dest, &real_size); + delete[] compressed; + + return real_size == sz; +} + +static void ReplayStop() { + lastExecFilename.clear(); + lastExecCommands.clear(); + lastExecPushbuf.clear(); +} + +bool RunMountedReplay(const std::string &filename) { + _assert_msg_(SYSTEM, !GPURecord::IsActivePending(), "Cannot run replay while recording."); + + Core_ListenStopRequest(&ReplayStop); + if (lastExecFilename != filename) { + PROFILE_THIS_SCOPE("ReplayLoad"); + u32 fp = pspFileSystem.OpenFile(filename, FILEACCESS_READ); + u8 header[8]{}; + int version = 0; + pspFileSystem.ReadFile(fp, header, sizeof(header)); + pspFileSystem.ReadFile(fp, (u8 *)&version, sizeof(version)); + + if (memcmp(header, HEADER, sizeof(header)) != 0 || version > VERSION || version < MIN_VERSION) { + ERROR_LOG(SYSTEM, "Invalid GE dump or unsupported version"); + pspFileSystem.CloseFile(fp); + return false; + } + + u32 sz = 0; + pspFileSystem.ReadFile(fp, (u8 *)&sz, sizeof(sz)); + u32 bufsz = 0; + pspFileSystem.ReadFile(fp, (u8 *)&bufsz, sizeof(bufsz)); + + lastExecCommands.resize(sz); + lastExecPushbuf.resize(bufsz); + + bool truncated = false; + truncated = truncated || !ReadCompressed(fp, lastExecCommands.data(), sizeof(Command) * sz); + truncated = truncated || !ReadCompressed(fp, lastExecPushbuf.data(), bufsz); + + pspFileSystem.CloseFile(fp); + + if (truncated) { + ERROR_LOG(SYSTEM, "Truncated GE dump"); + return false; + } + + lastExecFilename = filename; + } + + DumpExecute executor(lastExecPushbuf, lastExecCommands); + return executor.Run(); +} + +}; diff --git a/GPU/Debugger/Playback.h b/GPU/Debugger/Playback.h new file mode 100644 index 0000000000..433801cbf2 --- /dev/null +++ b/GPU/Debugger/Playback.h @@ -0,0 +1,26 @@ +// Copyright (c) 2017- 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 + +namespace GPURecord { + +bool RunMountedReplay(const std::string &filename); + +}; diff --git a/GPU/Debugger/Record.cpp b/GPU/Debugger/Record.cpp index 95e2df1466..8d1b216677 100644 --- a/GPU/Debugger/Record.cpp +++ b/GPU/Debugger/Record.cpp @@ -22,18 +22,13 @@ #include #include #include "base/stringutil.h" -#include "profiler/profiler.h" #include "Common/Common.h" #include "Common/FileUtil.h" #include "Common/Log.h" #include "Core/Core.h" -#include "Core/CoreTiming.h" #include "Core/ELF/ParamSFO.h" -#include "Core/FileSystems/MetaFileSystem.h" #include "Core/HLE/sceDisplay.h" -#include "Core/HLE/sceKernelMemory.h" #include "Core/MemMap.h" -#include "Core/MIPS/MIPS.h" #include "Core/System.h" #include "GPU/GPUInterface.h" #include "GPU/GPUState.h" @@ -41,335 +36,21 @@ #include "GPU/Common/TextureDecoder.h" #include "GPU/Common/VertexDecoderCommon.h" #include "GPU/Debugger/Record.h" +#include "GPU/Debugger/RecordFormat.h" namespace GPURecord { -static const char *HEADER = "PPSSPPGE"; -// Version 1: Uncompressed -// Version 2: Uses snappy -// Version 3: Adds FRAMEBUF0-FRAMEBUF9 -static const int VERSION = 3; -static const int MIN_VERSION = 2; - static bool active = false; static bool nextFrame = false; static int flipLastAction = -1; static std::function writeCallback; -enum class CommandType : u8 { - INIT = 0, - REGISTERS = 1, - VERTICES = 2, - INDICES = 3, - CLUT = 4, - TRANSFERSRC = 5, - MEMSET = 6, - MEMCPYDEST = 7, - MEMCPYDATA = 8, - DISPLAY = 9, - - TEXTURE0 = 0x10, - TEXTURE1 = 0x11, - TEXTURE2 = 0x12, - TEXTURE3 = 0x13, - TEXTURE4 = 0x14, - TEXTURE5 = 0x15, - TEXTURE6 = 0x16, - TEXTURE7 = 0x17, - - FRAMEBUF0 = 0x18, - FRAMEBUF1 = 0x19, - FRAMEBUF2 = 0x1A, - FRAMEBUF3 = 0x1B, - FRAMEBUF4 = 0x1C, - FRAMEBUF5 = 0x1D, - FRAMEBUF6 = 0x1E, - FRAMEBUF7 = 0x1F, -}; - -#pragma pack(push, 1) - -struct Command { - CommandType type; - u32 sz; - u32 ptr; -}; - -#pragma pack(pop) - static std::vector pushbuf; static std::vector commands; static std::vector lastRegisters; static std::vector lastTextures; static std::set lastRenderTargets; -// This class maps pushbuffer (dump data) sections to PSP memory. -// Dumps can be larger than available PSP memory, because they include generated data too. -// -// If possible, it maps to dynamically allocated "slabs" so nearby access is fast. -// Otherwise it uses "extra" allocations to manage sections that straddle two slabs. -// Slabs are managed with LRU, extra buffers are round-robin. -class BufMapping { -public: - BufMapping(const std::vector &pushbuf) : pushbuf_(pushbuf) { - } - - // Returns a pointer to contiguous memory for this access, or else 0 (failure). - u32 Map(u32 bufpos, u32 sz, const std::function &flush); - - // Clear and reset allocations made. - void Reset() { - slabGeneration_ = 0; - extraOffset_ = 0; - for (int i = 0; i < SLAB_COUNT; ++i) { - slabs_[i].Free(); - } - for (int i = 0; i < EXTRA_COUNT; ++i) { - extra_[i].Free(); - } - } - -protected: - u32 MapSlab(u32 bufpos, const std::function &flush); - u32 MapExtra(u32 bufpos, u32 sz, const std::function &flush); - - enum { - // These numbers kept low because we only have 24 MB of user memory to map into. - SLAB_SIZE = 1 * 1024 * 1024, - // 10 is the number of texture units + verts + inds. - // In the worst case, we could concurrently need 10 slabs/extras at the same time. - SLAB_COUNT = 10, - EXTRA_COUNT = 10, - }; - - // The current "generation". Static simply as a convenience for access. - // This increments on every allocation, for a simple LRU. - static int slabGeneration_; - - // An aligned large mapping of the pushbuffer in PSP RAM. - struct SlabInfo { - u32 psp_pointer_ = 0; - u32 buf_pointer_ = 0; - int last_used_ = 0; - - bool Matches(u32 bufpos) { - // We check psp_pointer_ because bufpos = 0 is valid, and the initial value. - return buf_pointer_ == bufpos && psp_pointer_ != 0; - } - - // Automatically marks used for LRU purposes. - u32 Ptr(u32 bufpos) { - last_used_ = slabGeneration_; - return psp_pointer_ + (bufpos - buf_pointer_); - } - - int Age() const { - // If not allocated, it's as expired as it's gonna get. - if (psp_pointer_ == 0) - return std::numeric_limits::max(); - return slabGeneration_ - last_used_; - } - - bool Alloc(); - void Free(); - bool Setup(u32 bufpos, const std::vector &pushbuf_); - }; - - // An adhoc mapping of the pushbuffer (either larger than a slab or straddling slabs.) - // Remember: texture data, verts, etc. must be contiguous. - struct ExtraInfo { - u32 psp_pointer_ = 0; - u32 buf_pointer_ = 0; - u32 size_ = 0; - - bool Matches(u32 bufpos, u32 sz) { - // We check psp_pointer_ because bufpos = 0 is valid, and the initial value. - return buf_pointer_ == bufpos && psp_pointer_ != 0 && size_ >= sz; - } - - u32 Ptr() { - return psp_pointer_; - } - - bool Alloc(u32 bufpos, u32 sz, const std::vector &pushbuf_); - void Free(); - }; - - SlabInfo slabs_[SLAB_COUNT]{}; - u32 extraOffset_ = 0; - ExtraInfo extra_[EXTRA_COUNT]{}; - - const std::vector &pushbuf_; -}; - -u32 BufMapping::Map(u32 bufpos, u32 sz, const std::function &flush) { - int slab1 = bufpos / SLAB_SIZE; - int slab2 = (bufpos + sz - 1) / SLAB_SIZE; - - if (slab1 == slab2) { - // Doesn't straddle, so we can just map to a slab. - return MapSlab(bufpos, flush); - } else { - // We need contiguous, so we'll just allocate separately. - return MapExtra(bufpos, sz, flush); - } -} - -u32 BufMapping::MapSlab(u32 bufpos, const std::function &flush) { - u32 slab_pos = (bufpos / SLAB_SIZE) * SLAB_SIZE; - - int best = 0; - for (int i = 0; i < SLAB_COUNT; ++i) { - if (slabs_[i].Matches(slab_pos)) { - return slabs_[i].Ptr(bufpos); - } - - if (slabs_[i].Age() > slabs_[best].Age()) { - best = i; - } - } - - // Stall before mapping a new slab. - flush(); - - // Okay, we need to allocate. - if (!slabs_[best].Setup(slab_pos, pushbuf_)) { - return 0; - } - return slabs_[best].Ptr(bufpos); -} - -u32 BufMapping::MapExtra(u32 bufpos, u32 sz, const std::function &flush) { - for (int i = 0; i < EXTRA_COUNT; ++i) { - // Might be likely to reuse larger buffers straddling slabs. - if (extra_[i].Matches(bufpos, sz)) { - return extra_[i].Ptr(); - } - } - - // Stall first, so we don't stomp existing RAM. - flush(); - - int i = extraOffset_; - extraOffset_ = (extraOffset_ + 1) % EXTRA_COUNT; - - if (!extra_[i].Alloc(bufpos, sz, pushbuf_)) { - // Let's try to power on - hopefully none of these are still in use. - for (int i = 0; i < EXTRA_COUNT; ++i) { - extra_[i].Free(); - } - if (!extra_[i].Alloc(bufpos, sz, pushbuf_)) { - return 0; - } - } - return extra_[i].Ptr(); -} - -bool BufMapping::SlabInfo::Alloc() { - u32 sz = SLAB_SIZE; - psp_pointer_ = userMemory.Alloc(sz, false, "Slab"); - if (psp_pointer_ == -1) { - psp_pointer_ = 0; - } - return psp_pointer_ != 0; -} - -void BufMapping::SlabInfo::Free() { - if (psp_pointer_) { - userMemory.Free(psp_pointer_); - psp_pointer_ = 0; - buf_pointer_ = 0; - last_used_ = 0; - } -} - -bool BufMapping::ExtraInfo::Alloc(u32 bufpos, u32 sz, const std::vector &pushbuf_) { - // Make sure we've freed any previous allocation first. - Free(); - - u32 allocSize = sz; - psp_pointer_ = userMemory.Alloc(allocSize, false, "Straddle extra"); - if (psp_pointer_ == -1) { - psp_pointer_ = 0; - } - if (psp_pointer_ == 0) { - return false; - } - - buf_pointer_ = bufpos; - size_ = sz; - Memory::MemcpyUnchecked(psp_pointer_, pushbuf_.data() + bufpos, sz); - return true; -} - -void BufMapping::ExtraInfo::Free() { - if (psp_pointer_) { - userMemory.Free(psp_pointer_); - psp_pointer_ = 0; - buf_pointer_ = 0; - } -} - -bool BufMapping::SlabInfo::Setup(u32 bufpos, const std::vector &pushbuf_) { - // If it already has RAM, we're simply taking it over. Slabs come only in one size. - if (psp_pointer_ == 0) { - if (!Alloc()) { - return false; - } - } - - buf_pointer_ = bufpos; - u32 sz = std::min((u32)SLAB_SIZE, (u32)pushbuf_.size() - bufpos); - Memory::MemcpyUnchecked(psp_pointer_, pushbuf_.data() + bufpos, sz); - - slabGeneration_++; - last_used_ = slabGeneration_; - return true; -} - -int BufMapping::slabGeneration_ = 0; - -// TODO: Maybe move execute to another file? -class DumpExecute { -public: - DumpExecute(const std::vector &pushbuf, const std::vector &commands) - : pushbuf_(pushbuf), commands_(commands), mapping_(pushbuf) { - } - ~DumpExecute(); - - bool Run(); - -private: - void SyncStall(); - bool SubmitCmds(const void *p, u32 sz); - void SubmitListEnd(); - - void Init(u32 ptr, u32 sz); - void Registers(u32 ptr, u32 sz); - void Vertices(u32 ptr, u32 sz); - void Indices(u32 ptr, u32 sz); - void Clut(u32 ptr, u32 sz); - void TransferSrc(u32 ptr, u32 sz); - void Memset(u32 ptr, u32 sz); - void MemcpyDest(u32 ptr, u32 sz); - void Memcpy(u32 ptr, u32 sz); - void Texture(int level, u32 ptr, u32 sz); - void Framebuf(int level, u32 ptr, u32 sz); - void Display(u32 ptr, u32 sz); - - u32 execMemcpyDest = 0; - u32 execListBuf = 0; - u32 execListPos = 0; - u32 execListID = 0; - const int LIST_BUF_SIZE = 256 * 1024; - std::vector execListQueue; - u16 lastBufw_[8]{}; - - const std::vector &pushbuf_; - const std::vector &commands_; - BufMapping mapping_; -}; - static void FlushRegisters() { if (!lastRegisters.empty()) { Command last{CommandType::REGISTERS}; @@ -879,401 +560,4 @@ void NotifyFrame() { } } -void DumpExecute::SyncStall() { - gpu->UpdateStall(execListID, execListPos); - s64 listTicks = gpu->GetListTicks(execListID); - if (listTicks != -1) { - s64 nowTicks = CoreTiming::GetTicks(); - if (listTicks > nowTicks) { - currentMIPS->downcount -= listTicks - nowTicks; - } - } - - // Make sure downcount doesn't overflow. - CoreTiming::ForceCheck(); -} - -bool DumpExecute::SubmitCmds(const void *p, u32 sz) { - if (execListBuf == 0) { - u32 allocSize = LIST_BUF_SIZE; - execListBuf = userMemory.Alloc(allocSize, "List buf"); - if (execListBuf == -1) { - execListBuf = 0; - } - if (execListBuf == 0) { - ERROR_LOG(SYSTEM, "Unable to allocate for display list"); - return false; - } - - execListPos = execListBuf; - Memory::Write_U32(GE_CMD_NOP << 24, execListPos); - execListPos += 4; - - gpu->EnableInterrupts(false); - auto optParam = PSPPointer::Create(0); - execListID = gpu->EnqueueList(execListBuf, execListPos, -1, optParam, false); - gpu->EnableInterrupts(true); - } - - u32 pendingSize = (int)execListQueue.size() * sizeof(u32); - // Validate space for jump. - u32 allocSize = pendingSize + sz + 8; - if (execListPos + allocSize >= execListBuf + LIST_BUF_SIZE) { - Memory::Write_U32((GE_CMD_BASE << 24) | ((execListBuf >> 8) & 0x00FF0000), execListPos); - Memory::Write_U32((GE_CMD_JUMP << 24) | (execListBuf & 0x00FFFFFF), execListPos + 4); - - execListPos = execListBuf; - - // Don't continue until we've stalled. - SyncStall(); - } - - Memory::MemcpyUnchecked(execListPos, execListQueue.data(), pendingSize); - execListPos += pendingSize; - u32 writePos = execListPos; - Memory::MemcpyUnchecked(execListPos, p, sz); - execListPos += sz; - - // TODO: Unfortunate. Maybe Texture commands should contain the bufw instead. - // The goal here is to realistically combine prims in dumps. Stalling for the bufw flushes. - u32_le *ops = (u32_le *)Memory::GetPointer(writePos); - for (u32 i = 0; i < sz / 4; ++i) { - u32 cmd = ops[i] >> 24; - if (cmd >= GE_CMD_TEXBUFWIDTH0 && cmd <= GE_CMD_TEXBUFWIDTH7) { - int level = cmd - GE_CMD_TEXBUFWIDTH0; - u16 bufw = ops[i] & 0xFFFF; - - // NOP the address part of the command to avoid a flush too. - if (bufw == lastBufw_[level]) - ops[i] = GE_CMD_NOP << 24; - else - ops[i] = (gstate.texbufwidth[level] & 0xFFFF0000) | bufw; - lastBufw_[level] = bufw; - } - - // Since we're here anyway, also NOP out texture addresses. - // This makes Step Tex not hit phantom textures. - if (cmd >= GE_CMD_TEXADDR0 && cmd <= GE_CMD_TEXADDR7) { - ops[i] = GE_CMD_NOP << 24; - } - } - - execListQueue.clear(); - - return true; -} - -void DumpExecute::SubmitListEnd() { - if (execListPos == 0) { - return; - } - - // There's always space for the end, same size as a jump. - Memory::Write_U32(GE_CMD_FINISH << 24, execListPos); - Memory::Write_U32(GE_CMD_END << 24, execListPos + 4); - execListPos += 8; - - SyncStall(); - gpu->ListSync(execListID, 0); -} - -void DumpExecute::Init(u32 ptr, u32 sz) { - gstate.Restore((u32_le *)(pushbuf_.data() + ptr)); - gpu->ReapplyGfxState(); -} - -void DumpExecute::Registers(u32 ptr, u32 sz) { - SubmitCmds(pushbuf_.data() + ptr, sz); -} - -void DumpExecute::Vertices(u32 ptr, u32 sz) { - u32 psp = mapping_.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); - if (psp == 0) { - ERROR_LOG(SYSTEM, "Unable to allocate for vertices"); - return; - } - - execListQueue.push_back((GE_CMD_BASE << 24) | ((psp >> 8) & 0x00FF0000)); - execListQueue.push_back((GE_CMD_VADDR << 24) | (psp & 0x00FFFFFF)); -} - -void DumpExecute::Indices(u32 ptr, u32 sz) { - u32 psp = mapping_.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); - if (psp == 0) { - ERROR_LOG(SYSTEM, "Unable to allocate for indices"); - return; - } - - execListQueue.push_back((GE_CMD_BASE << 24) | ((psp >> 8) & 0x00FF0000)); - execListQueue.push_back((GE_CMD_IADDR << 24) | (psp & 0x00FFFFFF)); -} - -void DumpExecute::Clut(u32 ptr, u32 sz) { - u32 psp = mapping_.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); - if (psp == 0) { - ERROR_LOG(SYSTEM, "Unable to allocate for clut"); - return; - } - - execListQueue.push_back((GE_CMD_CLUTADDRUPPER << 24) | ((psp >> 8) & 0x00FF0000)); - execListQueue.push_back((GE_CMD_CLUTADDR << 24) | (psp & 0x00FFFFFF)); -} - -void DumpExecute::TransferSrc(u32 ptr, u32 sz) { - u32 psp = mapping_.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); - if (psp == 0) { - ERROR_LOG(SYSTEM, "Unable to allocate for transfer"); - return; - } - - // Need to sync in order to access gstate.transfersrcw. - SyncStall(); - - execListQueue.push_back((gstate.transfersrcw & 0xFF00FFFF) | ((psp >> 8) & 0x00FF0000)); - execListQueue.push_back(((GE_CMD_TRANSFERSRC) << 24) | (psp & 0x00FFFFFF)); -} - -void DumpExecute::Memset(u32 ptr, u32 sz) { - PROFILE_THIS_SCOPE("ReplayMemset"); - struct MemsetCommand { - u32 dest; - int value; - u32 sz; - }; - - const MemsetCommand *data = (const MemsetCommand *)(pushbuf_.data() + ptr); - - if (Memory::IsVRAMAddress(data->dest)) { - SyncStall(); - gpu->PerformMemorySet(data->dest, (u8)data->value, data->sz); - } -} - -void DumpExecute::MemcpyDest(u32 ptr, u32 sz) { - execMemcpyDest = *(const u32 *)(pushbuf_.data() + ptr); -} - -void DumpExecute::Memcpy(u32 ptr, u32 sz) { - PROFILE_THIS_SCOPE("ReplayMemcpy"); - if (Memory::IsVRAMAddress(execMemcpyDest)) { - SyncStall(); - Memory::MemcpyUnchecked(execMemcpyDest, pushbuf_.data() + ptr, sz); - gpu->PerformMemoryUpload(execMemcpyDest, sz); - } -} - -void DumpExecute::Texture(int level, u32 ptr, u32 sz) { - u32 psp = mapping_.Map(ptr, sz, std::bind(&DumpExecute::SyncStall, this)); - if (psp == 0) { - ERROR_LOG(SYSTEM, "Unable to allocate for texture"); - return; - } - - u32 bufwCmd = GE_CMD_TEXBUFWIDTH0 + level; - u32 addrCmd = GE_CMD_TEXADDR0 + level; - execListQueue.push_back((bufwCmd << 24) | ((psp >> 8) & 0x00FF0000) | lastBufw_[level]); - execListQueue.push_back((addrCmd << 24) | (psp & 0x00FFFFFF)); -} - -void DumpExecute::Framebuf(int level, u32 ptr, u32 sz) { - PROFILE_THIS_SCOPE("ReplayFramebuf"); - struct FramebufData { - u32 addr; - int bufw; - u32 flags; - u32 pad; - }; - - FramebufData *framebuf = (FramebufData *)(pushbuf_.data() + ptr); - - u32 bufwCmd = GE_CMD_TEXBUFWIDTH0 + level; - u32 addrCmd = GE_CMD_TEXADDR0 + level; - execListQueue.push_back((bufwCmd << 24) | ((framebuf->addr >> 8) & 0x00FF0000) | framebuf->bufw); - execListQueue.push_back((addrCmd << 24) | (framebuf->addr & 0x00FFFFFF)); - lastBufw_[level] = framebuf->bufw; - - // And now also copy the data into VRAM (in case it wasn't actually rendered.) - u32 headerSize = (u32)sizeof(FramebufData); - u32 pspSize = sz - headerSize; - const bool isTarget = (framebuf->flags & 1) != 0; - // Could potentially always skip if !isTarget, but playing it safe for offset texture behavior. - if (Memory::IsValidRange(framebuf->addr, pspSize) && (!isTarget || !g_Config.bSoftwareRendering)) { - // Intentionally don't trigger an upload here. - Memory::MemcpyUnchecked(framebuf->addr, pushbuf_.data() + ptr + headerSize, pspSize); - } -} - -void DumpExecute::Display(u32 ptr, u32 sz) { - struct DisplayBufData { - PSPPointer topaddr; - int linesize, pixelFormat; - }; - - DisplayBufData *disp = (DisplayBufData *)(pushbuf_.data() + ptr); - - // Sync up drawing. - SyncStall(); - - __DisplaySetFramebuf(disp->topaddr.ptr, disp->linesize, disp->pixelFormat, 1); - __DisplaySetFramebuf(disp->topaddr.ptr, disp->linesize, disp->pixelFormat, 0); -} - -DumpExecute::~DumpExecute() { - execMemcpyDest = 0; - if (execListBuf) { - userMemory.Free(execListBuf); - execListBuf = 0; - } - execListPos = 0; - mapping_.Reset(); -} - -bool DumpExecute::Run() { - for (const Command &cmd : commands_) { - switch (cmd.type) { - case CommandType::INIT: - Init(cmd.ptr, cmd.sz); - break; - - case CommandType::REGISTERS: - Registers(cmd.ptr, cmd.sz); - break; - - case CommandType::VERTICES: - Vertices(cmd.ptr, cmd.sz); - break; - - case CommandType::INDICES: - Indices(cmd.ptr, cmd.sz); - break; - - case CommandType::CLUT: - Clut(cmd.ptr, cmd.sz); - break; - - case CommandType::TRANSFERSRC: - TransferSrc(cmd.ptr, cmd.sz); - break; - - case CommandType::MEMSET: - Memset(cmd.ptr, cmd.sz); - break; - - case CommandType::MEMCPYDEST: - MemcpyDest(cmd.ptr, cmd.sz); - break; - - case CommandType::MEMCPYDATA: - Memcpy(cmd.ptr, cmd.sz); - break; - - case CommandType::TEXTURE0: - case CommandType::TEXTURE1: - case CommandType::TEXTURE2: - case CommandType::TEXTURE3: - case CommandType::TEXTURE4: - case CommandType::TEXTURE5: - case CommandType::TEXTURE6: - case CommandType::TEXTURE7: - Texture((int)cmd.type - (int)CommandType::TEXTURE0, cmd.ptr, cmd.sz); - break; - - case CommandType::FRAMEBUF0: - case CommandType::FRAMEBUF1: - case CommandType::FRAMEBUF2: - case CommandType::FRAMEBUF3: - case CommandType::FRAMEBUF4: - case CommandType::FRAMEBUF5: - case CommandType::FRAMEBUF6: - case CommandType::FRAMEBUF7: - Framebuf((int)cmd.type - (int)CommandType::FRAMEBUF0, cmd.ptr, cmd.sz); - break; - - case CommandType::DISPLAY: - Display(cmd.ptr, cmd.sz); - break; - - default: - ERROR_LOG(SYSTEM, "Unsupported GE dump command: %d", (int)cmd.type); - return false; - } - } - - SubmitListEnd(); - return true; -} - -static bool ReadCompressed(u32 fp, void *dest, size_t sz) { - u32 compressed_size = 0; - if (pspFileSystem.ReadFile(fp, (u8 *)&compressed_size, sizeof(compressed_size)) != sizeof(compressed_size)) { - return false; - } - - u8 *compressed = new u8[compressed_size]; - if (pspFileSystem.ReadFile(fp, compressed, compressed_size) != compressed_size) { - delete [] compressed; - return false; - } - - size_t real_size = sz; - snappy_uncompress((const char *)compressed, compressed_size, (char *)dest, &real_size); - delete [] compressed; - - return real_size == sz; -} - -static std::string lastExecFilename; -static std::vector lastExecCommands; -static std::vector lastExecPushbuf; - -static void ReplayStop() { - lastExecFilename.clear(); - lastExecCommands.clear(); - lastExecPushbuf.clear(); -} - -bool RunMountedReplay(const std::string &filename) { - _assert_msg_(SYSTEM, !active && !nextFrame, "Cannot run replay while recording."); - - Core_ListenStopRequest(&ReplayStop); - if (lastExecFilename != filename) { - PROFILE_THIS_SCOPE("ReplayLoad"); - u32 fp = pspFileSystem.OpenFile(filename, FILEACCESS_READ); - u8 header[8]{}; - int version = 0; - pspFileSystem.ReadFile(fp, header, sizeof(header)); - pspFileSystem.ReadFile(fp, (u8 *)&version, sizeof(version)); - - if (memcmp(header, HEADER, sizeof(header)) != 0 || version > VERSION || version < MIN_VERSION) { - ERROR_LOG(SYSTEM, "Invalid GE dump or unsupported version"); - pspFileSystem.CloseFile(fp); - return false; - } - - u32 sz = 0; - pspFileSystem.ReadFile(fp, (u8 *)&sz, sizeof(sz)); - u32 bufsz = 0; - pspFileSystem.ReadFile(fp, (u8 *)&bufsz, sizeof(bufsz)); - - lastExecCommands.resize(sz); - lastExecPushbuf.resize(bufsz); - - bool truncated = false; - truncated = truncated || !ReadCompressed(fp, lastExecCommands.data(), sizeof(Command) * sz); - truncated = truncated || !ReadCompressed(fp, lastExecPushbuf.data(), bufsz); - - pspFileSystem.CloseFile(fp); - - if (truncated) { - ERROR_LOG(SYSTEM, "Truncated GE dump"); - return false; - } - - lastExecFilename = filename; - } - - DumpExecute executor(lastExecPushbuf, lastExecCommands); - return executor.Run(); -} - }; diff --git a/GPU/Debugger/Record.h b/GPU/Debugger/Record.h index 020ce2ca8c..ce15aa68d0 100644 --- a/GPU/Debugger/Record.h +++ b/GPU/Debugger/Record.h @@ -36,6 +36,4 @@ void NotifyUpload(u32 dest, u32 sz); void NotifyDisplay(u32 addr, int stride, int fmt); void NotifyFrame(); -bool RunMountedReplay(const std::string &filename); - }; diff --git a/GPU/Debugger/RecordFormat.h b/GPU/Debugger/RecordFormat.h new file mode 100644 index 0000000000..aeb7791fe2 --- /dev/null +++ b/GPU/Debugger/RecordFormat.h @@ -0,0 +1,72 @@ +// Copyright (c) 2017- 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 "Common/CommonTypes.h" + +namespace GPURecord { + +static const char *HEADER = "PPSSPPGE"; +// Version 1: Uncompressed +// Version 2: Uses snappy +// Version 3: Adds FRAMEBUF0-FRAMEBUF9 +static const int VERSION = 3; +static const int MIN_VERSION = 2; + +enum class CommandType : u8 { + INIT = 0, + REGISTERS = 1, + VERTICES = 2, + INDICES = 3, + CLUT = 4, + TRANSFERSRC = 5, + MEMSET = 6, + MEMCPYDEST = 7, + MEMCPYDATA = 8, + DISPLAY = 9, + + TEXTURE0 = 0x10, + TEXTURE1 = 0x11, + TEXTURE2 = 0x12, + TEXTURE3 = 0x13, + TEXTURE4 = 0x14, + TEXTURE5 = 0x15, + TEXTURE6 = 0x16, + TEXTURE7 = 0x17, + + FRAMEBUF0 = 0x18, + FRAMEBUF1 = 0x19, + FRAMEBUF2 = 0x1A, + FRAMEBUF3 = 0x1B, + FRAMEBUF4 = 0x1C, + FRAMEBUF5 = 0x1D, + FRAMEBUF6 = 0x1E, + FRAMEBUF7 = 0x1F, +}; + +#pragma pack(push, 1) + +struct Command { + CommandType type; + u32 sz; + u32 ptr; +}; + +#pragma pack(pop) + +}; diff --git a/GPU/GPU.vcxproj b/GPU/GPU.vcxproj index eabc46f4ba..af8feef17b 100644 --- a/GPU/GPU.vcxproj +++ b/GPU/GPU.vcxproj @@ -406,7 +406,9 @@ + + @@ -582,6 +584,7 @@ + diff --git a/GPU/GPU.vcxproj.filters b/GPU/GPU.vcxproj.filters index 76b0113d10..b6f35b5a2b 100644 --- a/GPU/GPU.vcxproj.filters +++ b/GPU/GPU.vcxproj.filters @@ -276,6 +276,12 @@ Debugger + + Debugger + + + Debugger + @@ -551,5 +557,8 @@ GLES + + Debugger + \ No newline at end of file diff --git a/UWP/GPU_UWP/GPU_UWP.vcxproj b/UWP/GPU_UWP/GPU_UWP.vcxproj index 9dd5a945de..73a9f1ae43 100644 --- a/UWP/GPU_UWP/GPU_UWP.vcxproj +++ b/UWP/GPU_UWP/GPU_UWP.vcxproj @@ -413,7 +413,9 @@ + + @@ -472,6 +474,7 @@ + diff --git a/UWP/GPU_UWP/GPU_UWP.vcxproj.filters b/UWP/GPU_UWP/GPU_UWP.vcxproj.filters index 5f71cb0d67..b519821e34 100644 --- a/UWP/GPU_UWP/GPU_UWP.vcxproj.filters +++ b/UWP/GPU_UWP/GPU_UWP.vcxproj.filters @@ -39,6 +39,7 @@ + @@ -91,7 +92,9 @@ + + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 47174b4a10..985b8efa8e 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -245,6 +245,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/GPU/Common/ShaderUniforms.cpp \ $(SRC)/GPU/Debugger/Breakpoints.cpp \ $(SRC)/GPU/Debugger/Debugger.cpp \ + $(SRC)/GPU/Debugger/Playback.cpp \ $(SRC)/GPU/Debugger/Record.cpp \ $(SRC)/GPU/Debugger/Stepping.cpp \ $(SRC)/GPU/GLES/FramebufferManagerGLES.cpp \ diff --git a/libretro/Makefile.common b/libretro/Makefile.common index e48ddbf990..cf2d07681c 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -169,6 +169,7 @@ SOURCES_CXX += \ $(COMMONDIR)/ColorConv.cpp \ $(GPUDIR)/Debugger/Breakpoints.cpp \ $(GPUDIR)/Debugger/Debugger.cpp \ + $(GPUDIR)/Debugger/Playback.cpp \ $(GPUDIR)/Debugger/Record.cpp \ $(GPUDIR)/Debugger/Stepping.cpp \ $(GPUDIR)/Common/TextureCacheCommon.cpp \ From aff429ee090d0a5b768d6d07166bc59e9197e60d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 24 Jun 2019 10:30:32 +0200 Subject: [PATCH 19/20] For the non-vulkan backends, zero display rotation settings just to be sure. --- Windows/GPU/D3D11Context.cpp | 4 ++++ Windows/GPU/D3D9Context.cpp | 5 +++++ android/jni/AndroidJavaGLContext.cpp | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/Windows/GPU/D3D11Context.cpp b/Windows/GPU/D3D11Context.cpp index 49cd1bbdf8..fef1438fb6 100644 --- a/Windows/GPU/D3D11Context.cpp +++ b/Windows/GPU/D3D11Context.cpp @@ -6,6 +6,7 @@ #include #include "base/logging.h" +#include "Base/display.h" #include "util/text/utf8.h" #include "i18n/i18n.h" @@ -51,6 +52,9 @@ void D3D11Context::SwapInterval(int interval) { HRESULT D3D11Context::CreateTheDevice(IDXGIAdapter *adapter) { bool windowed = true; + // D3D11 has no need for display rotation. + g_display_rotation = DisplayRotation::ROTATE_0; + g_display_rot_matrix.setIdentity(); #if defined(_DEBUG) && !defined(_M_ARM) && !defined(_M_ARM64) UINT createDeviceFlags = D3D11_CREATE_DEVICE_DEBUG; #else diff --git a/Windows/GPU/D3D9Context.cpp b/Windows/GPU/D3D9Context.cpp index c21a6d7c5d..4317fd1328 100644 --- a/Windows/GPU/D3D9Context.cpp +++ b/Windows/GPU/D3D9Context.cpp @@ -6,6 +6,7 @@ #include "gfx/d3d9_state.h" #include "base/logging.h" +#include "base/display.h" #include "util/text/utf8.h" #include "i18n/i18n.h" @@ -62,6 +63,10 @@ bool D3D9Context::Init(HINSTANCE hInst, HWND wnd, std::string *error_message) { bool windowed = true; hWnd_ = wnd; + // D3D9 has no need for display rotation. + g_display_rotation = DisplayRotation::ROTATE_0; + g_display_rot_matrix.setIdentity(); + DIRECT3DCREATE9EX g_pfnCreate9ex; hD3D9_ = LoadLibrary(TEXT("d3d9.dll")); diff --git a/android/jni/AndroidJavaGLContext.cpp b/android/jni/AndroidJavaGLContext.cpp index 2d463e76dc..f03c5b864d 100644 --- a/android/jni/AndroidJavaGLContext.cpp +++ b/android/jni/AndroidJavaGLContext.cpp @@ -1,6 +1,7 @@ #include #include "AndroidJavaGLContext.h" +#include "base/display.h" #include "base/NativeApp.h" #include "gfx_es2/gpu_features.h" #include "Core/ConfigValues.h" @@ -13,6 +14,9 @@ AndroidJavaEGLGraphicsContext::AndroidJavaEGLGraphicsContext() { bool AndroidJavaEGLGraphicsContext::InitFromRenderThread(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) { ILOG("AndroidJavaEGLGraphicsContext::InitFromRenderThread"); CheckGLExtensions(); + // OpenGL handles rotated rendering in the driver. + g_display_rotation = DisplayRotation::ROTATE_0; + g_display_rot_matrix.setIdentity(); draw_ = Draw::T3DCreateGLContext(); renderManager_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); bool success = draw_->CreatePresets(); From 2a664add313c006b8e05bee8d8762460aa5b1196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 24 Jun 2019 18:31:56 +0200 Subject: [PATCH 20/20] Vulkan: An additional check during init. May help #12131 --- Common/Vulkan/VulkanContext.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Common/Vulkan/VulkanContext.cpp b/Common/Vulkan/VulkanContext.cpp index 732351aa43..76c67ff9ed 100644 --- a/Common/Vulkan/VulkanContext.cpp +++ b/Common/Vulkan/VulkanContext.cpp @@ -105,6 +105,11 @@ VkResult VulkanContext::CreateInstance(const CreateInfo &info) { return VK_ERROR_INITIALIZATION_FAILED; } + if (!IsInstanceExtensionAvailable(VK_KHR_SURFACE_EXTENSION_NAME)) { + // Cannot create a Vulkan display without VK_KHR_SURFACE_EXTENSION. + init_error_ = "Vulkan not loaded - no surface extension"; + return VK_ERROR_INITIALIZATION_FAILED; + } flags_ = info.flags; // List extensions to try to enable.