1/*
2 * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26#import <stdlib.h>
27
28#import "sun_java2d_opengl_CGLSurfaceData.h"
29
30#import "JNIUtilities.h"
31#import "OGLRenderQueue.h"
32#import "CGLGraphicsConfig.h"
33#import "CGLSurfaceData.h"
34#import "ThreadUtilities.h"
35
36/* JDK's glext.h is already included and will prevent the Apple glext.h
37 * being included, so define the externs directly
38 */
39extern void glBindFramebufferEXT(GLenum target, GLuint framebuffer);
40extern CGLError CGLTexImageIOSurface2D(
41        CGLContextObj ctx, GLenum target, GLenum internal_format,
42        GLsizei width, GLsizei height, GLenum format, GLenum type,
43        IOSurfaceRef ioSurface, GLuint plane);
44
45/**
46 * The methods in this file implement the native windowing system specific
47 * layer (CGL) for the OpenGL-based Java 2D pipeline.
48 */
49
50#pragma mark -
51#pragma mark "--- Mac OS X specific methods for GL pipeline ---"
52
53// TODO: hack that's called from OGLRenderQueue to test out unlockFocus behavior
54#if 0
55void
56OGLSD_UnlockFocus(OGLContext *oglc, OGLSDOps *dstOps)
57{
58    CGLCtxInfo *ctxinfo = (CGLCtxInfo *)oglc->ctxInfo;
59    CGLSDOps *cglsdo = (CGLSDOps *)dstOps->privOps;
60    fprintf(stderr, "about to unlock focus: %p %p\n",
61            cglsdo->peerData, ctxinfo->context);
62
63    NSOpenGLView *nsView = cglsdo->peerData;
64    if (nsView != NULL) {
65JNI_COCOA_ENTER(env);
66        [nsView unlockFocus];
67JNI_COCOA_EXIT(env);
68    }
69}
70#endif
71
72/**
73 * Makes the given context current to its associated "scratch" surface.  If
74 * the operation is successful, this method will return JNI_TRUE; otherwise,
75 * returns JNI_FALSE.
76 */
77static jboolean
78CGLSD_MakeCurrentToScratch(JNIEnv *env, OGLContext *oglc)
79{
80    J2dTraceLn(J2D_TRACE_INFO, "CGLSD_MakeCurrentToScratch");
81
82    if (oglc == NULL) {
83        J2dRlsTraceLn(J2D_TRACE_ERROR,
84                      "CGLSD_MakeCurrentToScratch: context is null");
85        return JNI_FALSE;
86    }
87
88JNI_COCOA_ENTER(env);
89
90    CGLCtxInfo *ctxinfo = (CGLCtxInfo *)oglc->ctxInfo;
91#if USE_NSVIEW_FOR_SCRATCH
92    [ctxinfo->context makeCurrentContext];
93#else
94    [ctxinfo->context clearDrawable];
95    [ctxinfo->context makeCurrentContext];
96    [ctxinfo->context setPixelBuffer: ctxinfo->scratchSurface
97            cubeMapFace: 0
98            mipMapLevel: 0
99            currentVirtualScreen: [ctxinfo->context currentVirtualScreen]];
100#endif
101
102JNI_COCOA_EXIT(env);
103
104    return JNI_TRUE;
105}
106
107/**
108 * This function disposes of any native windowing system resources associated
109 * with this surface.
110 */
111void
112OGLSD_DestroyOGLSurface(JNIEnv *env, OGLSDOps *oglsdo)
113{
114    J2dTraceLn(J2D_TRACE_INFO, "OGLSD_DestroyOGLSurface");
115
116JNI_COCOA_ENTER(env);
117
118    CGLSDOps *cglsdo = (CGLSDOps *)oglsdo->privOps;
119    if (oglsdo->drawableType == OGLSD_WINDOW) {
120        // detach the NSView from the NSOpenGLContext
121        CGLGraphicsConfigInfo *cglInfo = cglsdo->configInfo;
122        OGLContext *oglc = cglInfo->context;
123        CGLCtxInfo *ctxinfo = (CGLCtxInfo *)oglc->ctxInfo;
124        [ctxinfo->context clearDrawable];
125    }
126
127    oglsdo->drawableType = OGLSD_UNDEFINED;
128
129JNI_COCOA_EXIT(env);
130}
131
132/**
133 * Makes the given GraphicsConfig's context current to its associated
134 * "scratch" surface.  If there is a problem making the context current,
135 * this method will return NULL; otherwise, returns a pointer to the
136 * OGLContext that is associated with the given GraphicsConfig.
137 */
138OGLContext *
139OGLSD_SetScratchSurface(JNIEnv *env, jlong pConfigInfo)
140{
141    J2dTraceLn(J2D_TRACE_INFO, "OGLSD_SetScratchContext");
142
143    CGLGraphicsConfigInfo *cglInfo = (CGLGraphicsConfigInfo *)jlong_to_ptr(pConfigInfo);
144    if (cglInfo == NULL) {
145        J2dRlsTraceLn(J2D_TRACE_ERROR, "OGLSD_SetScratchContext: cgl config info is null");
146        return NULL;
147    }
148
149    OGLContext *oglc = cglInfo->context;
150    if (oglc == NULL) {
151        J2dRlsTraceLn(J2D_TRACE_ERROR, "OGLSD_SetScratchContext: ogl context is null");
152        return NULL;
153    }
154
155    CGLCtxInfo *ctxinfo = (CGLCtxInfo *)oglc->ctxInfo;
156
157JNI_COCOA_ENTER(env);
158
159    // avoid changing the context's target view whenever possible, since
160    // calling setView causes flickering; as long as our context is current
161    // to some view, it's not necessary to switch to the scratch surface
162    if ([ctxinfo->context view] == nil) {
163        // it seems to be necessary to explicitly flush between context changes
164        OGLContext *currentContext = OGLRenderQueue_GetCurrentContext();
165        if (currentContext != NULL) {
166            j2d_glFlush();
167        }
168
169        if (!CGLSD_MakeCurrentToScratch(env, oglc)) {
170            return NULL;
171        }
172    // make sure our context is current
173    } else if ([NSOpenGLContext currentContext] != ctxinfo->context) {
174        [ctxinfo->context makeCurrentContext];
175    }
176
177    if (OGLC_IS_CAP_PRESENT(oglc, CAPS_EXT_FBOBJECT)) {
178        // the GL_EXT_framebuffer_object extension is present, so this call
179        // will ensure that we are bound to the scratch surface (and not
180        // some other framebuffer object)
181        j2d_glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
182    }
183
184JNI_COCOA_EXIT(env);
185
186    return oglc;
187}
188
189/**
190 * Makes a context current to the given source and destination
191 * surfaces.  If there is a problem making the context current, this method
192 * will return NULL; otherwise, returns a pointer to the OGLContext that is
193 * associated with the destination surface.
194 */
195OGLContext *
196OGLSD_MakeOGLContextCurrent(JNIEnv *env, OGLSDOps *srcOps, OGLSDOps *dstOps)
197{
198    J2dTraceLn(J2D_TRACE_INFO, "OGLSD_MakeOGLContextCurrent");
199
200    CGLSDOps *dstCGLOps = (CGLSDOps *)dstOps->privOps;
201
202    J2dTraceLn4(J2D_TRACE_VERBOSE, "  src: %d %p dst: %d %p", srcOps->drawableType, srcOps, dstOps->drawableType, dstOps);
203
204    OGLContext *oglc = dstCGLOps->configInfo->context;
205    if (oglc == NULL) {
206        J2dRlsTraceLn(J2D_TRACE_ERROR, "OGLSD_MakeOGLContextCurrent: context is null");
207        return NULL;
208    }
209
210    CGLCtxInfo *ctxinfo = (CGLCtxInfo *)oglc->ctxInfo;
211
212    // it seems to be necessary to explicitly flush between context changes
213    OGLContext *currentContext = OGLRenderQueue_GetCurrentContext();
214    if (currentContext != NULL) {
215        j2d_glFlush();
216    }
217
218    if (dstOps->drawableType == OGLSD_FBOBJECT) {
219        // first make sure we have a current context (if the context isn't
220        // already current to some drawable, we will make it current to
221        // its scratch surface)
222        if (oglc != currentContext) {
223            if (!CGLSD_MakeCurrentToScratch(env, oglc)) {
224                return NULL;
225            }
226        }
227
228        // now bind to the fbobject associated with the destination surface;
229        // this means that all rendering will go into the fbobject destination
230        // (note that we unbind the currently bound texture first; this is
231        // recommended procedure when binding an fbobject)
232        j2d_glBindTexture(GL_TEXTURE_2D, 0);
233        j2d_glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, dstOps->fbobjectID);
234
235        return oglc;
236    }
237
238JNI_COCOA_ENTER(env);
239
240    CGLSDOps *cglsdo = (CGLSDOps *)dstOps->privOps;
241    NSView *nsView = (NSView *)cglsdo->peerData;
242
243    if ([ctxinfo->context view] != nsView) {
244        [ctxinfo->context makeCurrentContext];
245        [ctxinfo->context setView: nsView];
246    }
247
248    if (OGLC_IS_CAP_PRESENT(oglc, CAPS_EXT_FBOBJECT)) {
249        // the GL_EXT_framebuffer_object extension is present, so we
250        // must bind to the default (windowing system provided)
251        // framebuffer
252        j2d_glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
253    }
254
255JNI_COCOA_EXIT(env);
256
257    return oglc;
258}
259
260/**
261 * This function initializes a native window surface and caches the window
262 * bounds in the given OGLSDOps.  Returns JNI_TRUE if the operation was
263 * successful; JNI_FALSE otherwise.
264 */
265jboolean
266OGLSD_InitOGLWindow(JNIEnv *env, OGLSDOps *oglsdo)
267{
268    J2dTraceLn(J2D_TRACE_INFO, "OGLSD_InitOGLWindow");
269
270    if (oglsdo == NULL) {
271        J2dRlsTraceLn(J2D_TRACE_ERROR, "OGLSD_InitOGLWindow: ops are null");
272        return JNI_FALSE;
273    }
274
275    CGLSDOps *cglsdo = (CGLSDOps *)oglsdo->privOps;
276    if (cglsdo == NULL) {
277        J2dRlsTraceLn(J2D_TRACE_ERROR, "OGLSD_InitOGLWindow: cgl ops are null");
278        return JNI_FALSE;
279    }
280
281    AWTView *v = cglsdo->peerData;
282    if (v == NULL) {
283        J2dRlsTraceLn(J2D_TRACE_ERROR, "OGLSD_InitOGLWindow: view is invalid");
284        return JNI_FALSE;
285    }
286
287JNI_COCOA_ENTER(env);
288    NSRect surfaceBounds = [v bounds];
289    oglsdo->drawableType = OGLSD_WINDOW;
290    oglsdo->isOpaque = JNI_TRUE;
291    oglsdo->width = surfaceBounds.size.width;
292    oglsdo->height = surfaceBounds.size.height;
293JNI_COCOA_EXIT(env);
294
295    J2dTraceLn2(J2D_TRACE_VERBOSE, "  created window: w=%d h=%d", oglsdo->width, oglsdo->height);
296
297    return JNI_TRUE;
298}
299
300void
301OGLSD_SwapBuffers(JNIEnv *env, jlong pPeerData)
302{
303    J2dTraceLn(J2D_TRACE_INFO, "OGLSD_SwapBuffers");
304
305JNI_COCOA_ENTER(env);
306    [[NSOpenGLContext currentContext] flushBuffer];
307JNI_COCOA_EXIT(env);
308}
309
310void
311OGLSD_Flush(JNIEnv *env)
312{
313    OGLSDOps *dstOps = OGLRenderQueue_GetCurrentDestination();
314    if (dstOps != NULL) {
315        CGLSDOps *dstCGLOps = (CGLSDOps *)dstOps->privOps;
316        CGLLayer *layer = (CGLLayer*)dstCGLOps->layer;
317        if (layer != NULL) {
318            [ThreadUtilities performOnMainThreadWaiting:NO block:^(){
319                AWT_ASSERT_APPKIT_THREAD;
320                [layer setNeedsDisplay];
321            }];
322        }
323    }
324}
325
326#pragma mark -
327#pragma mark "--- CGLSurfaceData methods ---"
328
329extern LockFunc        OGLSD_Lock;
330extern GetRasInfoFunc  OGLSD_GetRasInfo;
331extern UnlockFunc      OGLSD_Unlock;
332extern DisposeFunc     OGLSD_Dispose;
333
334JNIEXPORT void JNICALL
335Java_sun_java2d_opengl_CGLSurfaceData_initOps
336    (JNIEnv *env, jobject cglsd, jobject gc,
337     jlong pConfigInfo, jlong pPeerData, jlong layerPtr,
338     jint xoff, jint yoff, jboolean isOpaque)
339{
340    J2dTraceLn(J2D_TRACE_INFO, "CGLSurfaceData_initOps");
341    J2dTraceLn1(J2D_TRACE_INFO, "  pPeerData=%p", jlong_to_ptr(pPeerData));
342    J2dTraceLn2(J2D_TRACE_INFO, "  xoff=%d, yoff=%d", (int)xoff, (int)yoff);
343
344    gc = (*env)->NewGlobalRef(env, gc);
345    if (gc == NULL) {
346        JNU_ThrowOutOfMemoryError(env, "Initialization of SurfaceData failed.");
347        return;
348    }
349
350    OGLSDOps *oglsdo = (OGLSDOps *)
351        SurfaceData_InitOps(env, cglsd, sizeof(OGLSDOps));
352    if (oglsdo == NULL) {
353        (*env)->DeleteGlobalRef(env, gc);
354        JNU_ThrowOutOfMemoryError(env, "Initialization of SurfaceData failed.");
355        return;
356    }
357    // later the graphicsConfig will be used for deallocation of oglsdo
358    oglsdo->graphicsConfig = gc;
359
360    CGLSDOps *cglsdo = (CGLSDOps *)malloc(sizeof(CGLSDOps));
361    if (cglsdo == NULL) {
362        JNU_ThrowOutOfMemoryError(env, "creating native cgl ops");
363        return;
364    }
365
366    oglsdo->privOps = cglsdo;
367
368    oglsdo->sdOps.Lock               = OGLSD_Lock;
369    oglsdo->sdOps.GetRasInfo         = OGLSD_GetRasInfo;
370    oglsdo->sdOps.Unlock             = OGLSD_Unlock;
371    oglsdo->sdOps.Dispose            = OGLSD_Dispose;
372
373    oglsdo->drawableType = OGLSD_UNDEFINED;
374    oglsdo->activeBuffer = GL_FRONT;
375    oglsdo->needsInit = JNI_TRUE;
376    oglsdo->xOffset = xoff;
377    oglsdo->yOffset = yoff;
378    oglsdo->isOpaque = isOpaque;
379
380    cglsdo->peerData = (AWTView *)jlong_to_ptr(pPeerData);
381    cglsdo->layer = (CGLLayer *)jlong_to_ptr(layerPtr);
382    cglsdo->configInfo = (CGLGraphicsConfigInfo *)jlong_to_ptr(pConfigInfo);
383
384    if (cglsdo->configInfo == NULL) {
385        free(cglsdo);
386        JNU_ThrowNullPointerException(env, "Config info is null in initOps");
387    }
388}
389
390JNIEXPORT void JNICALL
391Java_sun_java2d_opengl_CGLSurfaceData_clearWindow
392(JNIEnv *env, jobject cglsd)
393{
394    J2dTraceLn(J2D_TRACE_INFO, "CGLSurfaceData_clearWindow");
395
396    OGLSDOps *oglsdo = (OGLSDOps*) SurfaceData_GetOps(env, cglsd);
397    CGLSDOps *cglsdo = (CGLSDOps*) oglsdo->privOps;
398
399    cglsdo->peerData = NULL;
400    cglsdo->layer = NULL;
401}
402
403#pragma mark -
404#pragma mark "--- CGLSurfaceData methods - Mac OS X specific ---"
405
406// Must be called on the QFT...
407JNIEXPORT void JNICALL
408Java_sun_java2d_opengl_CGLSurfaceData_validate
409    (JNIEnv *env, jobject jsurfacedata,
410     jint xoff, jint yoff, jint width, jint height, jboolean isOpaque)
411{
412    J2dTraceLn2(J2D_TRACE_INFO, "CGLSurfaceData_validate: w=%d h=%d", width, height);
413
414    OGLSDOps *oglsdo = (OGLSDOps*)SurfaceData_GetOps(env, jsurfacedata);
415    oglsdo->needsInit = JNI_TRUE;
416    oglsdo->xOffset = xoff;
417    oglsdo->yOffset = yoff;
418
419    oglsdo->width = width;
420    oglsdo->height = height;
421    oglsdo->isOpaque = isOpaque;
422
423    if (oglsdo->drawableType == OGLSD_WINDOW) {
424        OGLContext_SetSurfaces(env, ptr_to_jlong(oglsdo), ptr_to_jlong(oglsdo));
425
426        // we have to explicitly tell the NSOpenGLContext that its target
427        // drawable has changed size
428        CGLSDOps *cglsdo = (CGLSDOps *)oglsdo->privOps;
429        OGLContext *oglc = cglsdo->configInfo->context;
430        CGLCtxInfo *ctxinfo = (CGLCtxInfo *)oglc->ctxInfo;
431
432JNI_COCOA_ENTER(env);
433        [ctxinfo->context update];
434JNI_COCOA_EXIT(env);
435    }
436}
437