@@ -109,9 +109,41 @@ static void propsPutInt(CFMutableDictionaryRef props, const CFStringRef key, int
109109 CFRelease (boxedValue);
110110}
111111
112- static bool rlawtCreateIOSurface (JNIEnv *env, AWTContext *ctx) {
113- CGFloat scale = ctx->layer .superlayer .contentsScale ;
114- CGSize size = ctx->layer .frame .size ;
112+ // -- IOSurfacePool helpers --
113+
114+ static void poolInit (IOSurfacePool *pool) {
115+ memset (pool, 0 , sizeof (*pool));
116+ GLuint textures[RLAWT_MAX_POOL], framebuffers[RLAWT_MAX_POOL];
117+ glGenTextures (RLAWT_MAX_POOL, textures);
118+ glGenFramebuffers (RLAWT_MAX_POOL, framebuffers);
119+ for (int i = 0 ; i < RLAWT_MAX_POOL; i++) {
120+ pool->entries [i].tex = textures[i];
121+ pool->entries [i].fbo = framebuffers[i];
122+ }
123+ }
124+
125+ static void poolDestroy (IOSurfacePool *pool) {
126+ for (int i = 0 ; i < RLAWT_MAX_POOL; i++) {
127+ glDeleteTextures (1 , &pool->entries [i].tex );
128+ glDeleteFramebuffers (1 , &pool->entries [i].fbo );
129+ if (pool->entries [i].surface ) {
130+ CFRelease (pool->entries [i].surface );
131+ }
132+ }
133+ }
134+
135+ static bool poolEntryMatchesSize (IOSurfaceEntry *entry, CALayer *layer) {
136+ if (!entry->surface ) return false ;
137+ CGFloat scale = entry->scale ;
138+ CGSize size = layer.frame .size ;
139+ return IOSurfaceGetWidth (entry->surface ) == (size_t )(size.width * scale)
140+ && IOSurfaceGetHeight (entry->surface ) == (size_t )(size.height * scale)
141+ && layer.superlayer .contentsScale == scale;
142+ }
143+
144+ static bool poolCreateSurface (JNIEnv *env, IOSurfaceEntry *entry, CALayer *layer, CGLContextObj cglCtx) {
145+ CGFloat scale = layer.superlayer .contentsScale ;
146+ CGSize size = layer.frame .size ;
115147 size.width *= scale;
116148 size.height *= scale;
117149
@@ -127,20 +159,20 @@ static bool rlawtCreateIOSurface(JNIEnv *env, AWTContext *ctx) {
127159 rlawtThrow (env, " unable to create io surface" );
128160 return false ;
129161 }
130-
162+
131163 const GLuint target = GL_TEXTURE_RECTANGLE;
132164 glActiveTexture (GL_TEXTURE0);
133- glBindTexture (target, ctx ->tex [ctx->back] );
165+ glBindTexture (target, entry ->tex );
134166 CGLError err = CGLTexImageIOSurface2D (
135- ctx-> context ,
167+ cglCtx ,
136168 target, GL_RGBA,
137169 size.width , size.height ,
138170 GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
139- buf,
171+ buf,
140172 0 );
141173 glBindTexture (target, 0 );
142- glBindFramebuffer (GL_FRAMEBUFFER, ctx ->fbo [ctx->back] );
143- glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, ctx ->tex [ctx->back] , 0 );
174+ glBindFramebuffer (GL_FRAMEBUFFER, entry ->fbo );
175+ glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, entry ->tex , 0 );
144176
145177 if (err != kCGLNoError ) {
146178 rlawtThrowCGLError (env, " unable to bind io surface to texture" , err);
@@ -155,17 +187,39 @@ static bool rlawtCreateIOSurface(JNIEnv *env, AWTContext *ctx) {
155187 goto freeSurface;
156188 }
157189
158- if (ctx-> buffer [ctx->back] ) {
159- CFRelease (ctx-> buffer [ctx->back] );
190+ if (entry-> surface ) {
191+ CFRelease (entry-> surface );
160192 }
161- ctx-> buffer [ctx->back] = buf;
162- ctx-> bufferScale [ctx->back] = scale;
193+ entry-> surface = buf;
194+ entry-> scale = scale;
163195 return true ;
164196freeSurface:
165197 CFRelease (buf);
166198 return false ;
167199}
168200
201+ static int poolNextBack (IOSurfacePool *pool) {
202+ // Find any non-front surface the compositor isn't using
203+ for (int i = 0 ; i < pool->count ; i++) {
204+ if (i == pool->front ) continue ;
205+ if (pool->entries [i].surface && !IOSurfaceIsInUse (pool->entries [i].surface )) {
206+ return i;
207+ }
208+ }
209+
210+ // Grow pool if nothing was available
211+ if (pool->count < RLAWT_MAX_POOL) {
212+ return pool->count ++;
213+ }
214+
215+ // Pool full, all in use -- evict first non-front entry.
216+ // count >= 2 is guaranteed here (pool grows on first swap), so this always finds one.
217+ for (int i = 0 ; i < pool->count ; i++) {
218+ if (i != pool->front ) return i;
219+ }
220+ return 0 ; // unreachable
221+ }
222+
169223JNIEXPORT void JNICALL Java_net_runelite_rlawt_AWTContext_createGLContext (JNIEnv *env, jobject self) {
170224 AWTContext *ctx = rlawtGetContext (env, self);
171225 if (!ctx || !rlawtContextState (env, ctx, false )) {
@@ -215,8 +269,7 @@ JNIEXPORT void JNICALL Java_net_runelite_rlawt_AWTContext_createGLContext(JNIEnv
215269 goto freeContext;
216270 }
217271
218- glGenTextures (2 , &ctx->tex [0 ]);
219- glGenFramebuffers (2 , &ctx->fbo [0 ]);
272+ poolInit (&ctx->pool );
220273
221274 dispatch_sync (dispatch_get_main_queue (), ^{
222275 RLLayer *layer = [[RLLayer alloc ] init ];
@@ -237,7 +290,8 @@ JNIEXPORT void JNICALL Java_net_runelite_rlawt_AWTContext_createGLContext(JNIEnv
237290 dsi->bounds .height );
238291 });
239292
240- if (!rlawtCreateIOSurface (env, ctx)) {
293+ ctx->pool .count = 1 ;
294+ if (!poolCreateSurface (env, &ctx->pool .entries [0 ], ctx->layer , ctx->context )) {
241295 goto freeContext;
242296 }
243297
@@ -257,12 +311,7 @@ void rlawtContextFreePlatform(JNIEnv *env, AWTContext *ctx) {
257311 if (ctx->context ) {
258312 CGLDestroyContext (ctx->context );
259313 }
260- if (ctx->buffer [0 ]) {
261- CFRelease (ctx->buffer [0 ]);
262- }
263- if (ctx->buffer [1 ]) {
264- CFRelease (ctx->buffer [1 ]);
265- }
314+ poolDestroy (&ctx->pool );
266315 if (ctx->layer ) {
267316 dispatch_sync (dispatch_get_main_queue (), ^{
268317 [ctx->layer removeFromSuperlayer ];
@@ -300,20 +349,26 @@ JNIEXPORT void JNICALL Java_net_runelite_rlawt_AWTContext_swapBuffers(JNIEnv *en
300349 }
301350
302351 glFlush ();
352+
353+ IOSurfacePool *pool = &ctx->pool ;
354+
355+ // Present the current back buffer
356+ pool->front = pool->back ;
303357 RLLayer *rlLayer = (RLLayer*) ctx->layer ;
304- rlLayer->newScale = ctx-> bufferScale [ctx->back] ;
358+ rlLayer->newScale = pool-> entries [pool->front]. scale ;
305359 [rlLayer performSelectorOnMainThread:
306360 @selector (displayIOSurface: )
307- withObject: (id )(ctx->buffer[ctx->back] )
361+ withObject: (id )(pool->entries[pool->front].surface )
308362 waitUntilDone: true ];
309-
310- ctx->back ^= 1 ;
311-
312- if (!ctx->buffer [ctx->back]
313- || IOSurfaceGetWidth (ctx->buffer [ctx->back]) != (size_t ) (ctx->layer .frame .size .width * ctx->bufferScale [ctx->back])
314- || IOSurfaceGetHeight (ctx->buffer [ctx->back]) != (size_t ) (ctx->layer .frame .size .height * ctx->bufferScale [ctx->back])
315- || ctx->layer .superlayer .contentsScale != ctx->bufferScale [ctx->back]) {
316- if (!rlawtCreateIOSurface (env, ctx)) {
363+
364+ // Find a free surface for the next frame
365+ pool->back = poolNextBack (pool);
366+ IOSurfaceEntry *back = &pool->entries [pool->back];
367+
368+ // Create/recreate surface if needed (new slot, wrong size, or still held by compositor)
369+ if (!poolEntryMatchesSize (back, ctx->layer )
370+ || IOSurfaceIsInUse (back->surface )) {
371+ if (!poolCreateSurface (env, back, ctx->layer , ctx->context )) {
317372 return ;
318373 }
319374 }
@@ -325,7 +380,10 @@ JNIEXPORT jint JNICALL Java_net_runelite_rlawt_AWTContext_getFramebuffer(JNIEnv
325380 return 0 ;
326381 }
327382
328- return ctx->fbo [ctx->back ^ front];
383+ if (front) {
384+ return ctx->pool .entries [ctx->pool.front].fbo ;
385+ }
386+ return ctx->pool .entries [ctx->pool.back].fbo ;
329387}
330388
331389#endif
0 commit comments