Skip to content
Merged
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
8 changes: 8 additions & 0 deletions Ports/iOSPort/nativeSources/CN1Metalcompat.h
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,14 @@ id<MTLDevice> CN1MetalDevice(void);
// drawing.
id<MTLCommandQueue> CN1MetalCommandQueue(void);

// Called once by METALView.initWithCoder (main thread) to publish the
// view's MTLDevice + MTLCommandQueue. CN1MetalDevice / CN1MetalCommandQueue
// then return these without touching any UIView property. Keeping the
// queue identity stable with METALView is required: mutable-image setup
// commits share-and-FIFO with the screen render command buffer, so
// drawImage of a mutable can rely on its prior writes being ordered.
void CN1MetalSetDeviceAndCommandQueue(id<MTLDevice> device, id<MTLCommandQueue> queue);

// -------- Phase 3 v2: mutable-image rendering --------
//
// Mutable images render via the same ExecutableOp queue as the screen.
Expand Down
50 changes: 46 additions & 4 deletions Ports/iOSPort/nativeSources/CN1Metalcompat.m
Original file line number Diff line number Diff line change
Expand Up @@ -159,14 +159,56 @@ void CN1MetalEndFrame(void) {
int CN1MetalFramebufferWidth(void) { return currentFramebufferWidth; }
int CN1MetalFramebufferHeight(void) { return currentFramebufferHeight; }

// Process-lifetime device + command queue cache. The original implementation
// dereferenced [[GLViewController instance] eaglView].layer to fetch the
// CAMetalLayer's device on every call, but -[UIView layer] is a main-thread-
// only API. Paint runs on the Codename One EDT (a GCD background queue), and
// any drawShape → createAlphaMask → nativePathRendererCreateTexture path
// reaches CN1MetalDevice() from that thread. With Main Thread Checker
// enabled Xcode aborts the process before the first form renders.
//
// METALView publishes its device + command queue once at initWithCoder time
// (main thread) via CN1MetalSetDeviceAndCommandQueue; thereafter the two
// accessors return those statics without touching any UIView property. The
// queue identity must remain the same one METALView uses for screen
// rendering: mutable-image setup command buffers commit to this queue and
// rely on FIFO ordering with the screen render command buffer so subsequent
// drawImage(mutable) samples land after the mutable's writes. Spinning up
// a separate queue here (e.g. via newCommandQueue against the cached device)
// breaks that ordering — Apple's cross-queue dependency tracker did not
// preserve the visible result in the iOS Metal screenshot suite (the
// DialogTheme TextureBackdropPainter's cached-stripe image rendered only
// behind the dialog, not below it).
static id<MTLDevice> cachedMetalDevice = nil;
static id<MTLCommandQueue> cachedMetalCommandQueue = nil;

void CN1MetalSetDeviceAndCommandQueue(id<MTLDevice> device, id<MTLCommandQueue> queue) {
#ifdef CN1_USE_ARC
cachedMetalDevice = device;
cachedMetalCommandQueue = queue;
#else
// MRR: hold our own retain so both stay alive even after METALView
// releases its references at process exit. Both are process-lifetime
// singletons; no matching release is intended.
if (cachedMetalDevice != device) {
[device retain];
[cachedMetalDevice release];
cachedMetalDevice = device;
}
if (cachedMetalCommandQueue != queue) {
[queue retain];
[cachedMetalCommandQueue release];
cachedMetalCommandQueue = queue;
}
#endif
}

id<MTLDevice> CN1MetalDevice(void) {
METALView *mv = (METALView *)[[CodenameOne_GLViewController instance] eaglView];
return ((CAMetalLayer *)mv.layer).device;
return cachedMetalDevice;
}

id<MTLCommandQueue> CN1MetalCommandQueue(void) {
METALView *mv = (METALView *)[[CodenameOne_GLViewController instance] eaglView];
return mv.commandQueue;
return cachedMetalCommandQueue;
}

// --------------- Matrix state ---------------
Expand Down
16 changes: 15 additions & 1 deletion Ports/iOSPort/nativeSources/METALView.m
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,15 @@ - (id)initWithCoder:(NSCoder*)coder
metalLayer.device = MTLCreateSystemDefaultDevice();
metalLayer.opaque = TRUE;
metalLayer.pixelFormat = MTLPixelFormatBGRA8Unorm;
metalLayer.framebufferOnly = YES;
// framebufferOnly must be NO: presentFramebuffer blits screenTexture
// into the drawable via copyFromTexture:toTexture:, and Metal's blit
// validation aborts ("destinationTexture must not be a framebufferOnly
// texture") when the destination drawable was framebufferOnly. Debug
// builds with Metal API Validation enabled crash on the first paint;
// release builds silently produced undefined-behaviour copies on some
// GPUs. Trading the (small) memoryless-storage benefit for a working
// present path.
metalLayer.framebufferOnly = NO;
// Colour space for the Metal layer. Default is sRGB so colours
// match the GL path's CAEAGLLayer output: without it, CG-rasterised
// images and gradients (DeviceRGB-tagged in their CGBitmapContext)
Expand Down Expand Up @@ -186,6 +194,12 @@ - (id)initWithCoder:(NSCoder*)coder
#ifndef CN1_USE_ARC
[newQueue release];
#endif
// Publish the device + queue to CN1Metalcompat so its global
// accessors don't have to dereference our (UIView) layer from
// background threads. Doing it on the main thread, exactly once,
// means CN1MetalDevice / CN1MetalCommandQueue become cheap static
// reads safe to invoke from the EDT and any background GCD queue.
CN1MetalSetDeviceAndCommandQueue(metalLayer.device, self.commandQueue);
CGSize sz = self.bounds.size;
CGFloat s = self.contentScaleFactor;
[self updateFrameBufferSize:(int)(sz.width * s) h:(int)(sz.height * s)];
Expand Down
Loading