@@ -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