Skip to content

Commit 61b5d93

Browse files
committed
Fixed some clipping issues
1 parent f596a1e commit 61b5d93

File tree

3 files changed

+131
-33
lines changed

3 files changed

+131
-33
lines changed

Ports/iOSPort/nativeSources/ClipRect.m

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -103,27 +103,10 @@ -(void)execute {
103103
return;
104104
}
105105

106+
// In Metal, don't clamp clips to drawingRect - allow full screen clipping
107+
// The Java layer sends proper background fills, and clamping causes issues
108+
// with partial repaints and buffer synchronization
106109
clipIsTexture = NO;
107-
int x2 = x + width;
108-
int y2 = y + height;
109-
int orX = drawingRect.origin.x;
110-
int orY = drawingRect.origin.y;
111-
if(x < orX) {
112-
x = orX;
113-
width = x2 - x;
114-
}
115-
if(y < orY) {
116-
y = orY;
117-
height = y2 - y;
118-
}
119-
int destX2 = (int)(drawingRect.origin.x + drawingRect.size.width);
120-
int destY2 = (int)(drawingRect.origin.y + drawingRect.size.height);
121-
if(x2 > destX2) {
122-
width = destX2 - x;
123-
}
124-
if(y2 > destY2) {
125-
height = destY2 - y;
126-
}
127110

128111
if(width > 0 && height > 0) {
129112
[super clipBlock:NO];
@@ -154,9 +137,46 @@ -(void)execute {
154137

155138
[ClipRect updateClipToScale];
156139

140+
// Note: Input coordinates appear to already be in physical pixels (pre-scaled)
141+
// Just clamp to drawable bounds for safety (Metal requires this)
142+
CGSize drawableSize = [[[CodenameOne_GLViewController instance] metalView] drawableSize];
143+
int drawableW = (int)drawableSize.width;
144+
int drawableH = (int)drawableSize.height;
145+
146+
int finalX = clipX;
147+
int finalY = clipY;
148+
int finalW = clipW;
149+
int finalH = clipH;
150+
151+
if (finalX < 0) {
152+
finalW += finalX;
153+
finalX = 0;
154+
}
155+
if (finalY < 0) {
156+
finalH += finalY;
157+
finalY = 0;
158+
}
159+
if (finalX + finalW > drawableW) {
160+
finalW = drawableW - finalX;
161+
}
162+
if (finalY + finalH > drawableH) {
163+
finalH = drawableH - finalY;
164+
}
165+
if (finalW < 0) finalW = 0;
166+
if (finalH < 0) finalH = 0;
167+
if (finalX >= drawableW || finalY >= drawableH || finalW == 0 || finalH == 0) {
168+
// Scissor is completely outside drawable - block drawing
169+
[super clipBlock:YES];
170+
return;
171+
}
172+
157173
// Apply scissor to Metal encoder
158-
MTLScissorRect scissor = {clipX, clipY, clipW, clipH};
159-
[[CodenameOne_GLViewController instance] setScissorRect:scissor enabled:YES];
174+
MTLScissorRect scissor = {finalX, finalY, finalW, finalH};
175+
176+
// Match ES2 behavior: only apply scissor when no transform is active
177+
if (currentScaleX == 1 && currentScaleY == 1) {
178+
[[CodenameOne_GLViewController instance] setScissorRect:scissor enabled:YES];
179+
}
160180

161181
clipApplied = YES;
162182
} else {

Ports/iOSPort/nativeSources/METALView.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
@property (nonatomic, readonly) CGSize drawableSize;
5151
@property (nonatomic, assign) MTLScissorRect scissorRect;
5252
@property (nonatomic, assign) BOOL scissorEnabled;
53+
@property (nonatomic, strong) id<MTLTexture> persistentTexture;
54+
@property (nonatomic, assign) BOOL persistentTextureNeedsClear;
5355

5456
-(void)textViewDidChange:(UITextView *)textView;
5557
-(void)deleteFramebuffer;

Ports/iOSPort/nativeSources/METALView.m

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ @implementation METALView
5252
@synthesize drawable;
5353
@synthesize peerComponentsLayer;
5454
@synthesize currentEncoder;
55+
@synthesize persistentTexture;
5556

5657
- (CGSize)drawableSize {
5758
CAMetalLayer *layer = (CAMetalLayer *)self.layer;
@@ -128,7 +129,9 @@ - (id)initWithCoder:(NSCoder*)coder
128129
metalLayer.device = self.device;
129130
metalLayer.opaque = TRUE;
130131
metalLayer.pixelFormat = MTLPixelFormatBGRA8Unorm;
131-
metalLayer.framebufferOnly = YES;
132+
metalLayer.framebufferOnly = NO; // Allow blit operations on drawable
133+
// Use triple buffering for better CPU/GPU pipelining
134+
metalLayer.maximumDrawableCount = 3;
132135
self.commandQueue = [self.device newCommandQueue];
133136

134137
}
@@ -139,6 +142,8 @@ - (id)initWithCoder:(NSCoder*)coder
139142
- (void)dealloc
140143
{
141144
#ifndef CN1_USE_ARC
145+
// Property setter handles release when set to nil
146+
self.persistentTexture = nil;
142147
[super dealloc];
143148
#endif
144149
}
@@ -157,19 +162,72 @@ -(void)updateFrameBufferSize:(int)w h:(int)h {
157162

158163
}
159164

165+
-(void)ensurePersistentTexture:(CGSize)size {
166+
// Validate size - can't create 0x0 textures
167+
if (size.width <= 0 || size.height <= 0) {
168+
return;
169+
}
170+
171+
// Create or resize persistent texture to match drawable size
172+
if (self.persistentTexture == nil ||
173+
self.persistentTexture.width != (NSUInteger)size.width ||
174+
self.persistentTexture.height != (NSUInteger)size.height) {
175+
176+
MTLTextureDescriptor *desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
177+
width:(NSUInteger)size.width
178+
height:(NSUInteger)size.height
179+
mipmapped:NO];
180+
desc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
181+
desc.storageMode = MTLStorageModePrivate;
182+
183+
// newTextureWithDescriptor returns a retained object (+1)
184+
// Property setter will release old and retain new, so we need to release our +1
185+
id<MTLTexture> newTexture = [self.device newTextureWithDescriptor:desc];
186+
self.persistentTexture = newTexture;
187+
#ifndef CN1_USE_ARC
188+
[newTexture release]; // Balance the +1 from newTextureWithDescriptor
189+
#endif
190+
// Mark that this new texture needs to be cleared before first use
191+
self.persistentTextureNeedsClear = YES;
192+
}
193+
}
194+
160195
-(void)createRenderPassDescriptor {
161196
CAMetalLayer *layer = (CAMetalLayer*)self.layer;
197+
198+
// Get drawable first - its texture has the correct size even if layer.drawableSize is 0
162199
self.drawable = [layer nextDrawable];
163200
if (self.drawable == nil) {
164-
NSLog(@"METALView: Failed to get drawable");
201+
return;
202+
}
203+
204+
// Get size from drawable texture (more reliable than layer.drawableSize)
205+
CGSize size = CGSizeMake(self.drawable.texture.width, self.drawable.texture.height);
206+
207+
// Skip if size is invalid (during initialization)
208+
if (size.width <= 0 || size.height <= 0) {
209+
return;
210+
}
211+
212+
// Ensure persistent texture exists - this is our actual render target
213+
[self ensurePersistentTexture:size];
214+
if (self.persistentTexture == nil) {
165215
return;
166216
}
167217

168218
self.renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
169219
MTLRenderPassColorAttachmentDescriptor* colorAttachment = self.renderPassDescriptor.colorAttachments[0];
170-
colorAttachment.texture = self.drawable.texture;
171-
colorAttachment.loadAction = MTLLoadActionClear;
172-
colorAttachment.clearColor = MTLClearColorMake(1.0, 1.0, 1.0, 1.0);
220+
// Render to persistent texture (not drawable) - content is naturally preserved
221+
colorAttachment.texture = self.persistentTexture;
222+
223+
// Clear on first use, then load to preserve content
224+
if (self.persistentTextureNeedsClear) {
225+
colorAttachment.loadAction = MTLLoadActionClear;
226+
colorAttachment.clearColor = MTLClearColorMake(1.0, 1.0, 1.0, 1.0); // White background
227+
self.persistentTextureNeedsClear = NO;
228+
} else {
229+
colorAttachment.loadAction = MTLLoadActionLoad;
230+
}
173231
colorAttachment.storeAction = MTLStoreActionStore;
174232

175233
// Note: Blending is configured on MTLRenderPipelineDescriptor, not here
@@ -181,12 +239,16 @@ - (void)beginFrame
181239
[self setFramebuffer];
182240

183241
// Create render command encoder for this frame
242+
// We render directly to persistentTexture, so content is naturally preserved
184243
if (self.renderPassDescriptor != nil && self.commandBuffer != nil) {
185244
self.currentEncoder = [self.commandBuffer renderCommandEncoderWithDescriptor:self.renderPassDescriptor];
186245

187-
// Apply scissor rectangle if enabled
188-
if (self.scissorEnabled && self.currentEncoder != nil) {
189-
[self.currentEncoder setScissorRect:self.scissorRect];
246+
// Reset scissor to full texture at frame start (matches ES2 behavior)
247+
self.scissorEnabled = NO;
248+
if (self.currentEncoder != nil) {
249+
CGSize drawableSize = [self drawableSize];
250+
MTLScissorRect fullRect = {0, 0, (NSUInteger)drawableSize.width, (NSUInteger)drawableSize.height};
251+
[self.currentEncoder setScissorRect:fullRect];
190252
}
191253
}
192254
}
@@ -213,17 +275,31 @@ - (void)setFramebuffer
213275

214276
- (BOOL)presentFramebuffer
215277
{
216-
// End encoding before presenting
278+
// End render encoding before blitting
217279
if (self.currentEncoder) {
218280
[self.currentEncoder endEncoding];
219281
self.currentEncoder = nil;
220282
}
221283

222-
if (self.commandBuffer && self.drawable) {
284+
if (self.commandBuffer && self.drawable && self.persistentTexture) {
285+
// Single blit: copy from persistent texture to drawable for display
286+
CGSize size = [self drawableSize];
287+
id<MTLBlitCommandEncoder> blitEncoder = [self.commandBuffer blitCommandEncoder];
288+
[blitEncoder copyFromTexture:self.persistentTexture
289+
sourceSlice:0
290+
sourceLevel:0
291+
sourceOrigin:MTLOriginMake(0, 0, 0)
292+
sourceSize:MTLSizeMake(size.width, size.height, 1)
293+
toTexture:self.drawable.texture
294+
destinationSlice:0
295+
destinationLevel:0
296+
destinationOrigin:MTLOriginMake(0, 0, 0)];
297+
[blitEncoder endEncoding];
298+
223299
[self.commandBuffer presentDrawable:self.drawable];
224300
[self.commandBuffer commit];
225301

226-
// Clear for next frame
302+
// Clear for next frame (property setters handle release for retain/strong)
227303
self.commandBuffer = nil;
228304
self.renderPassDescriptor = nil;
229305
self.drawable = nil;

0 commit comments

Comments
 (0)