Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/purchase-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,20 @@ jobs:
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Preinstall Android emulator package
run: |
set -euo pipefail
printf 'y\n%.0s' {1..100} | sdkmanager --licenses >/dev/null || true
for attempt in 1 2 3; do
if sdkmanager --install emulator --channel=0; then
exit 0
fi
echo "Android emulator package install failed on attempt ${attempt}; retrying"
rm -rf "${ANDROID_SDK_ROOT:-/usr/local/lib/android/sdk}/.temp" || true
sleep 10
done
echo "::error::Unable to install Android emulator package after retries"
exit 1
- name: Run Android purchase instrumentation test (targeted)
uses: reactivecircus/android-emulator-runner@v2
with:
Expand Down
3 changes: 3 additions & 0 deletions Ports/iOSPort/nativeSources/CN1RenderingView.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
- (void)keyboardDoneClicked;
- (void)keyboardNextClicked;
- (void)textFieldDidChange;
@optional
- (void)invalidateRetainedFramebuffer;
- (void)prepareRetainedFramebufferForDrawRect:(CGRect)rect displayWidth:(int)displayWidth displayHeight:(int)displayHeight;
@end

#endif
7 changes: 7 additions & 0 deletions Ports/iOSPort/nativeSources/CodenameOne_GLAppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#endif

extern BOOL isIOS10();
extern void repaintUI();
int pendingRemoteNotificationRegistrations = 0;

BOOL isAppSuspended = NO;
Expand Down Expand Up @@ -290,6 +291,12 @@ - (void)cn1ApplicationWillEnterForeground
com_codename1_impl_ios_IOSImplementation_applicationWillEnterForeground__(CN1_THREAD_GET_STATE_PASS_SINGLE_ARG);
CodenameOne_GLViewController* vc = [CodenameOne_GLViewController instance];
if (vc != nil) {
#ifdef CN1_USE_METAL
id renderingView = [vc eaglView];
if ([renderingView respondsToSelector:@selector(invalidateRetainedFramebuffer)]) {
[renderingView invalidateRetainedFramebuffer];
}
#endif
// Defer to the next runloop so UIKit can settle the view bounds
// after the snapshot rotation. updateCanvas itself also
// orientation-validates the bounds for an extra safety net under
Expand Down
19 changes: 17 additions & 2 deletions Ports/iOSPort/nativeSources/CodenameOne_GLViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -4299,7 +4299,22 @@ - (void)drawFrame:(CGRect)rect allowInactive:(BOOL)allowInactive
return;
}
#endif
[[self eaglView] setFramebuffer];
id renderingView = [self eaglView];
#ifdef CN1_USE_METAL
BOOL hasScreenOps = NO;
if (currentTarget != nil) {
for (ExecutableOp *ex in currentTarget) {
if ([ex target] == nil) {
hasScreenOps = YES;
break;
}
}
}
if (hasScreenOps && [renderingView respondsToSelector:@selector(prepareRetainedFramebufferForDrawRect:displayWidth:displayHeight:)]) {
[renderingView prepareRetainedFramebufferForDrawRect:rect displayWidth:displayWidth displayHeight:displayHeight];
}
#endif
[renderingView setFramebuffer];
GLErrorLog;
BOOL drewContentOps = NO;
if(currentTarget != nil) {
Expand Down Expand Up @@ -4446,7 +4461,7 @@ - (void)drawFrame:(CGRect)rect allowInactive:(BOOL)allowInactive
}
GLErrorLog;

[[self eaglView] presentFramebuffer];
[renderingView presentFramebuffer];
GLErrorLog;
if (drewContentOps && !cn1LaunchPlaceholderDone) {
// First frame with real content is on screen -- fade out the launch
Expand Down
11 changes: 10 additions & 1 deletion Ports/iOSPort/nativeSources/METALView.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@
// turn or by the next normal presentFramebuffer -- whichever lands first.
// Read/written on the main thread only, so no synchronisation is needed.
BOOL needsResizePresent;

// The persistent screenTexture is private GPU storage. iOS may discard or
// leave it stale across app suspension, and CN1 can resume with only
// partial dirty-region flushes. Mark it invalid on foreground; the first
// full-screen repaint clears it before drawing fresh content.
BOOL retainedFramebufferInvalid;
BOOL clearRetainedFramebufferOnNextFrame;
}
@property (nonatomic, retain) id<MTLCommandQueue> commandQueue;
@property (nonatomic, retain) id<MTLCommandBuffer> commandBuffer;
Expand Down Expand Up @@ -82,9 +89,11 @@
- (BOOL)presentFramebuffer;
-(void)presentPreservedFrameIfNeeded;
-(void)updateFrameBufferSize:(int)w h:(int)h;
-(void)invalidateRetainedFramebuffer;
-(void)prepareRetainedFramebufferForDrawRect:(CGRect)rect displayWidth:(int)displayWidth displayHeight:(int)displayHeight;
-(void)textFieldDidChange;
-(void) keyboardDoneClicked;
-(void) keyboardNextClicked;
-(void) addPeerComponent:(UIView*) view;
@end
#endif
#endif
40 changes: 38 additions & 2 deletions Ports/iOSPort/nativeSources/METALView.m
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,36 @@ -(void)presentPreservedFrameIfNeeded {
[presentCb commit];
}

-(void)invalidateRetainedFramebuffer {
retainedFramebufferInvalid = YES;
clearRetainedFramebufferOnNextFrame = NO;
// A preserved resize frame is also stale after a suspend/resume cycle.
needsResizePresent = NO;
}

-(void)prepareRetainedFramebufferForDrawRect:(CGRect)rect displayWidth:(int)displayWidth displayHeight:(int)displayHeight {
if (!retainedFramebufferInvalid) {
return;
}
// During rotation, displayWidth/displayHeight can briefly lag the Metal
// drawable. Only consume the invalidation when the repaint covers the
// actual retained framebuffer we are about to load from.
int targetWidth = framebufferWidth > 0 ? framebufferWidth : displayWidth;
int targetHeight = framebufferHeight > 0 ? framebufferHeight : displayHeight;
if (targetWidth <= 0 || targetHeight <= 0) {
return;
}
CGFloat minX = rect.origin.x;
CGFloat minY = rect.origin.y;
CGFloat maxX = rect.origin.x + rect.size.width;
CGFloat maxY = rect.origin.y + rect.size.height;
if (minX <= 0.0f && minY <= 0.0f &&
maxX >= (CGFloat)targetWidth && maxY >= (CGFloat)targetHeight) {
clearRetainedFramebufferOnNextFrame = YES;
retainedFramebufferInvalid = NO;
}
}

-(void)createRenderPassDescriptor {
if (self.screenTexture == nil) {
self.renderPassDescriptor = nil;
Expand All @@ -532,7 +562,13 @@ -(void)createRenderPassDescriptor {
// which would wipe everything each frame) — CN1 only queues diff ops
// per frame; the OpenGL path relies on its renderbuffer persisting.
colorAttachment.texture = self.screenTexture;
colorAttachment.loadAction = MTLLoadActionLoad;
if (clearRetainedFramebufferOnNextFrame) {
colorAttachment.loadAction = MTLLoadActionClear;
colorAttachment.clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0);
clearRetainedFramebufferOnNextFrame = NO;
} else {
colorAttachment.loadAction = MTLLoadActionLoad;
}
colorAttachment.storeAction = MTLStoreActionStore;
// Attach the Stencil8 texture for polygon-shape clipping (#3921).
// Cleared at the start of every frame and discarded at the end --
Expand Down Expand Up @@ -775,4 +811,4 @@ -(void)layoutSubviews


@end
#endif
#endif
Loading