egl: Simplify _eglBindContext.
[mesa.git] / src / egl / main / eglcontext.c
index 1d12a405cfc896f20b7a009015b3ef15556f9502..bc22913d401dd7ef0fce7023c7033a2d0b57e85d 100644 (file)
 #include <string.h>
 #include "eglconfig.h"
 #include "eglcontext.h"
-#include "egldriver.h"
-#include "eglglobals.h"
-#include "eglhash.h"
+#include "egldisplay.h"
+#include "eglcurrent.h"
 #include "eglsurface.h"
+#include "egllog.h"
 
 
 /**
- * Initialize the given _EGLContext object to defaults.
+ * Return the API bit (one of EGL_xxx_BIT) of the context.
  */
-void
-_eglInitContext(_EGLContext *ctx)
+static EGLint
+_eglGetContextAPIBit(_EGLContext *ctx)
 {
-   /* just init to zero for now */
-   memset(ctx, 0, sizeof(_EGLContext));
+   EGLint bit = 0;
+
+   switch (ctx->ClientAPI) {
+   case EGL_OPENGL_ES_API:
+      switch (ctx->ClientVersion) {
+      case 1:
+         bit = EGL_OPENGL_ES_BIT;
+         break;
+      case 2:
+         bit = EGL_OPENGL_ES2_BIT;
+         break;
+      default:
+         break;
+      }
+      break;
+   case EGL_OPENVG_API:
+      bit = EGL_OPENVG_BIT;
+      break;
+   case EGL_OPENGL_API:
+      bit = EGL_OPENGL_BIT;
+      break;
+   default:
+      break;
+   }
+
+   return bit;
 }
 
 
-/*
- * Assign an EGLContext handle to the _EGLContext object then put it into
- * the hash table.
+/**
+ * Parse the list of context attributes and return the proper error code.
  */
-void
-_eglSaveContext(_EGLContext *ctx)
+static EGLint
+_eglParseContextAttribList(_EGLContext *ctx, const EGLint *attrib_list)
 {
-   assert(ctx);
-   ctx->Handle = _eglHashGenKey(_eglGlobal.Contexts);
-   _eglHashInsert(_eglGlobal.Contexts, ctx->Handle, ctx);
+   EGLenum api = ctx->ClientAPI;
+   EGLint i, err = EGL_SUCCESS;
+
+   if (!attrib_list)
+      return EGL_SUCCESS;
+
+   for (i = 0; attrib_list[i] != EGL_NONE; i++) {
+      EGLint attr = attrib_list[i++];
+      EGLint val = attrib_list[i];
+
+      switch (attr) {
+      case EGL_CONTEXT_CLIENT_VERSION:
+         if (api != EGL_OPENGL_ES_API) {
+            err = EGL_BAD_ATTRIBUTE;
+            break;
+         }
+         if (val != 1 && val != 2) {
+            err = EGL_BAD_ATTRIBUTE;
+            break;
+         }
+         ctx->ClientVersion = val;
+         break;
+      default:
+         err = EGL_BAD_ATTRIBUTE;
+         break;
+      }
+
+      if (err != EGL_SUCCESS) {
+         _eglLog(_EGL_DEBUG, "bad context attribute 0x%04x", attr);
+         break;
+      }
+   }
+
+   return err;
 }
 
 
 /**
- * Remove the given _EGLContext object from the hash table.
+ * Initialize the given _EGLContext object to defaults and/or the values
+ * in the attrib_list.
  */
-void
-_eglRemoveContext(_EGLContext *ctx)
+EGLBoolean
+_eglInitContext(_EGLContext *ctx, _EGLDisplay *dpy, _EGLConfig *conf,
+                const EGLint *attrib_list)
 {
-   _eglHashRemove(_eglGlobal.Contexts, ctx->Handle);
+   const EGLenum api = eglQueryAPI();
+   EGLint err;
+
+   if (api == EGL_NONE) {
+      _eglError(EGL_BAD_MATCH, "eglCreateContext(no client API)");
+      return EGL_FALSE;
+   }
+
+   memset(ctx, 0, sizeof(_EGLContext));
+   ctx->Resource.Display = dpy;
+   ctx->ClientAPI = api;
+   ctx->Config = conf;
+   ctx->WindowRenderBuffer = EGL_NONE;
+
+   ctx->ClientVersion = 1; /* the default, per EGL spec */
+
+   err = _eglParseContextAttribList(ctx, attrib_list);
+   if (err == EGL_SUCCESS && ctx->Config) {
+      EGLint renderable_type, api_bit;
+
+      renderable_type = GET_CONFIG_ATTRIB(ctx->Config, EGL_RENDERABLE_TYPE);
+      api_bit = _eglGetContextAPIBit(ctx);
+      if (!(renderable_type & api_bit)) {
+         _eglLog(_EGL_DEBUG, "context api is 0x%x while config supports 0x%x",
+               api_bit, renderable_type);
+         err = EGL_BAD_CONFIG;
+      }
+   }
+   if (err != EGL_SUCCESS)
+      return _eglError(err, "eglCreateContext");
+
+   return EGL_TRUE;
 }
 
 
 /**
- * Return the _EGLContext object that corresponds to the given
- * EGLContext handle.
+ * Just a placeholder/demo function.  Real driver will never use this!
  */
 _EGLContext *
-_eglLookupContext(EGLContext ctx)
+_eglCreateContext(_EGLDriver *drv, _EGLDisplay *dpy, _EGLConfig *conf,
+                  _EGLContext *share_list, const EGLint *attrib_list)
 {
-   _EGLContext *c = (_EGLContext *) _eglHashLookup(_eglGlobal.Contexts, ctx);
-   return c;
+   return NULL;
 }
 
 
 /**
- * Return the currently bound _EGLContext object, or NULL.
+ * Default fallback routine - drivers should usually override this.
  */
-_EGLContext *
-_eglGetCurrentContext(void)
+EGLBoolean
+_eglDestroyContext(_EGLDriver *drv, _EGLDisplay *dpy, _EGLContext *ctx)
+{
+   if (!_eglIsContextBound(ctx))
+      free(ctx);
+   return EGL_TRUE;
+}
+
+
+#ifdef EGL_VERSION_1_2
+static EGLint
+_eglQueryContextRenderBuffer(_EGLContext *ctx)
 {
-   /* XXX this should be per-thread someday */
-   return _eglGlobal.CurrentContext;
+   _EGLSurface *surf = ctx->DrawSurface;
+   EGLint rb;
+
+   if (!surf)
+      return EGL_NONE;
+   if (surf->Type == EGL_WINDOW_BIT && ctx->WindowRenderBuffer != EGL_NONE)
+      rb = ctx->WindowRenderBuffer;
+   else
+      rb = surf->RenderBuffer;
+   return rb;
+}
+#endif /* EGL_VERSION_1_2 */
+
+
+EGLBoolean
+_eglQueryContext(_EGLDriver *drv, _EGLDisplay *dpy, _EGLContext *c,
+                 EGLint attribute, EGLint *value)
+{
+   (void) drv;
+   (void) dpy;
+
+   if (!value)
+      return _eglError(EGL_BAD_PARAMETER, "eglQueryContext");
+
+   switch (attribute) {
+   case EGL_CONFIG_ID:
+      *value = GET_CONFIG_ATTRIB(c->Config, EGL_CONFIG_ID);
+      break;
+   case EGL_CONTEXT_CLIENT_VERSION:
+      *value = c->ClientVersion;
+      break;
+#ifdef EGL_VERSION_1_2
+   case EGL_CONTEXT_CLIENT_TYPE:
+      *value = c->ClientAPI;
+      break;
+   case EGL_RENDER_BUFFER:
+      *value = _eglQueryContextRenderBuffer(c);
+      break;
+#endif /* EGL_VERSION_1_2 */
+   default:
+      return _eglError(EGL_BAD_ATTRIBUTE, "eglQueryContext");
+   }
+
+   return EGL_TRUE;
 }
 
 
 /**
- * Just a placeholder/demo function.  Real driver will never use this!
+ * Bind the context to the thread and return the previous context.
+ *
+ * Note that the context may be NULL.
  */
-EGLContext
-_eglCreateContext(_EGLDriver *drv, EGLDisplay dpy, EGLConfig config, EGLContext share_list, const EGLint *attrib_list)
+static _EGLContext *
+_eglBindContextToThread(_EGLContext *ctx, _EGLThreadInfo *t)
 {
-   _EGLConfig *conf = _eglLookupConfig(drv, dpy, config);
-   if (!conf) {
-      _eglError(EGL_BAD_CONFIG, "eglCreateContext");
-      return EGL_NO_CONTEXT;
-   }
+   EGLint apiIndex;
+   _EGLContext *oldCtx;
 
-   if (share_list != EGL_NO_CONTEXT) {
-      _EGLContext *shareCtx = _eglLookupContext(share_list);
-      if (!shareCtx) {
-         _eglError(EGL_BAD_CONTEXT, "eglCreateContext(share_list)");
-         return EGL_NO_CONTEXT;
-      }
+   apiIndex = (ctx) ?
+      _eglConvertApiToIndex(ctx->ClientAPI) : t->CurrentAPIIndex;
+
+   oldCtx = t->CurrentContexts[apiIndex];
+   if (ctx != oldCtx) {
+      if (oldCtx)
+         oldCtx->Binding = NULL;
+      if (ctx)
+         ctx->Binding = t;
+
+      t->CurrentContexts[apiIndex] = ctx;
    }
 
-   return EGL_NO_CONTEXT;
+   return oldCtx;
 }
 
 
 /**
- * Default fallback routine - drivers should usually override this.
+ * Return true if the given context and surfaces can be made current.
  */
-EGLBoolean
-_eglDestroyContext(_EGLDriver *drv, EGLDisplay dpy, EGLContext ctx)
+static EGLBoolean
+_eglCheckMakeCurrent(_EGLContext *ctx, _EGLSurface *draw, _EGLSurface *read)
 {
-   _EGLContext *context = _eglLookupContext(ctx);
-   if (context) {
-      _eglHashRemove(_eglGlobal.Contexts, ctx);
-      if (context->IsBound) {
-         context->DeletePending = EGL_TRUE;
-      }
-      else {
-         free(context);
-      }
+   _EGLThreadInfo *t = _eglGetCurrentThread();
+   _EGLDisplay *dpy;
+   EGLint conflict_api;
+   EGLBoolean surfaceless;
+
+   if (_eglIsCurrentThreadDummy())
+      return _eglError(EGL_BAD_ALLOC, "eglMakeCurrent");
+
+   /* this is easy */
+   if (!ctx) {
+      if (draw || read)
+         return _eglError(EGL_BAD_MATCH, "eglMakeCurrent");
       return EGL_TRUE;
    }
-   else {
-      _eglError(EGL_BAD_CONTEXT, "eglDestroyContext");
-      return EGL_TRUE;
+
+   dpy = ctx->Resource.Display;
+   switch (_eglGetContextAPIBit(ctx)) {
+   case EGL_OPENGL_ES_BIT:
+      surfaceless = dpy->Extensions.KHR_surfaceless_gles1;
+      break;
+   case EGL_OPENGL_ES2_BIT:
+      surfaceless = dpy->Extensions.KHR_surfaceless_gles2;
+      break;
+   case EGL_OPENGL_BIT:
+      surfaceless = dpy->Extensions.KHR_surfaceless_opengl;
+      break;
+   default:
+      surfaceless = EGL_FALSE;
+      break;
    }
-}
 
+   if (!surfaceless && (draw == NULL || read == NULL))
+      return _eglError(EGL_BAD_MATCH, "eglMakeCurrent");
 
-EGLBoolean
-_eglQueryContext(_EGLDriver *drv, EGLDisplay dpy, EGLContext ctx, EGLint attribute, EGLint *value)
-{
-   _EGLContext *c = _eglLookupContext(ctx);
+   /* context stealing from another thread is not allowed */
+   if (ctx->Binding && ctx->Binding != t)
+      return _eglError(EGL_BAD_ACCESS, "eglMakeCurrent");
 
-   (void) drv;
-   (void) dpy;
+   /*
+    * The spec says
+    *
+    * "If ctx is current to some other thread, or if either draw or read are
+    * bound to contexts in another thread, an EGL_BAD_ACCESS error is
+    * generated."
+    *
+    * But it also says
+    *
+    * "at most one context may be bound to a particular surface at a given
+    * time"
+    *
+    * The latter is more restrictive so we can check only the latter case.
+    */
+   if ((draw && draw->CurrentContext && draw->CurrentContext != ctx) ||
+       (read && read->CurrentContext && read->CurrentContext != ctx))
+      return _eglError(EGL_BAD_ACCESS, "eglMakeCurrent");
 
-   if (!c) {
-      _eglError(EGL_BAD_CONTEXT, "eglQueryContext");
-      return EGL_FALSE;
-   }
+   /* simply require the configs to be equal */
+   if ((draw && draw->Config != ctx->Config) ||
+       (read && read->Config != ctx->Config))
+      return _eglError(EGL_BAD_MATCH, "eglMakeCurrent");
 
-   switch (attribute) {
-   case EGL_CONFIG_ID:
-      *value = GET_CONFIG_ATTRIB(c->Config, EGL_CONFIG_ID);
-      return EGL_TRUE;
+   switch (ctx->ClientAPI) {
+#ifdef EGL_VERSION_1_4
+   /* OpenGL and OpenGL ES are conflicting */
+   case EGL_OPENGL_ES_API:
+      conflict_api = EGL_OPENGL_API;
+      break;
+   case EGL_OPENGL_API:
+      conflict_api = EGL_OPENGL_ES_API;
+      break;
+#endif
    default:
-      _eglError(EGL_BAD_ATTRIBUTE, "eglQueryContext");
-      return EGL_FALSE;
+      conflict_api = -1;
+      break;
    }
+
+   if (conflict_api >= 0 && _eglGetAPIContext(conflict_api))
+      return _eglError(EGL_BAD_ACCESS, "eglMakeCurrent");
+
+   return EGL_TRUE;
 }
 
 
 /**
- * Drivers will typically call this to do the error checking and
- * update the various IsBound and DeletePending flags.
- * Then, the driver will do its device-dependent Make-Current stuff.
+ * Bind the context to the current thread and given surfaces.  Return the
+ * "orphaned" context and surfaces.  Each argument is both input and output.
  */
 EGLBoolean
-_eglMakeCurrent(_EGLDriver *drv, EGLDisplay dpy, EGLSurface d, EGLSurface r, EGLContext context)
+_eglBindContext(_EGLContext **ctx, _EGLSurface **draw, _EGLSurface **read)
 {
-   _EGLContext *ctx = _eglLookupContext(context);
-   _EGLSurface *draw = _eglLookupSurface(d);
-   _EGLSurface *read = _eglLookupSurface(r);
-
-   _EGLContext *oldContext = _eglGetCurrentContext();
-   _EGLSurface *oldDrawSurface = _eglGetCurrentSurface(EGL_DRAW);
-   _EGLSurface *oldReadSurface = _eglGetCurrentSurface(EGL_READ);
-
-   /* error checking */
-   if (ctx) {
-      if (draw == NULL || read == NULL) {
-         _eglError(EGL_BAD_MATCH, "eglMakeCurrent");
-         return EGL_FALSE;
-      }
-      if (draw->Config != ctx->Config) {
-         _eglError(EGL_BAD_MATCH, "eglMakeCurrent");
-         return EGL_FALSE;
-      }
-      if (read->Config != ctx->Config) {
-         _eglError(EGL_BAD_MATCH, "eglMakeCurrent");
-         return EGL_FALSE;
-      }
-   }
+   _EGLThreadInfo *t = _eglGetCurrentThread();
+   _EGLContext *newCtx = *ctx, *oldCtx;
+   _EGLSurface *newDraw = *draw, *newRead = *read;
 
-   /*
-    * check if the old context or surfaces need to be deleted
-    */
-   if (oldDrawSurface != NULL) {
-      oldDrawSurface->IsBound = EGL_FALSE;
-      if (oldDrawSurface->DeletePending) {
-         /* make sure we don't try to rebind a deleted surface */
-         if (draw == oldDrawSurface || draw == oldReadSurface) {
-            draw = NULL;
-         }
-         /* really delete surface now */
-         drv->DestroySurface(drv, dpy, oldDrawSurface->Handle);
-      }
-   }
-   if (oldReadSurface != NULL && oldReadSurface != oldDrawSurface) {
-      oldReadSurface->IsBound = EGL_FALSE;
-      if (oldReadSurface->DeletePending) {
-         /* make sure we don't try to rebind a deleted surface */
-         if (read == oldDrawSurface || read == oldReadSurface) {
-            read = NULL;
-         }
-         /* really delete surface now */
-         drv->DestroySurface(drv, dpy, oldReadSurface->Handle);
-      }
-   }
-   if (oldContext != NULL) {
-      oldContext->IsBound = EGL_FALSE;
-      if (oldContext->DeletePending) {
-         /* make sure we don't try to rebind a deleted context */
-         if (ctx == oldContext) {
-            ctx = NULL;
-         }
-         /* really delete context now */
-         drv->DestroyContext(drv, dpy, oldContext->Handle);
-      }
+   if (!_eglCheckMakeCurrent(newCtx, newDraw, newRead))
+      return EGL_FALSE;
+
+   /* bind the new context */
+   oldCtx = _eglBindContextToThread(newCtx, t);
+
+   /* break old bindings */
+   if (oldCtx) {
+      *ctx = oldCtx;
+      *draw = oldCtx->DrawSurface;
+      *read = oldCtx->ReadSurface;
+
+      if (*draw)
+         (*draw)->CurrentContext = NULL;
+      if (*read)
+         (*read)->CurrentContext = NULL;
+
+      oldCtx->DrawSurface = NULL;
+      oldCtx->ReadSurface = NULL;
    }
 
-   if (ctx) {
-      /* check read/draw again, in case we deleted them above */
-      if (draw == NULL || read == NULL) {
-         _eglError(EGL_BAD_MATCH, "eglMakeCurrent");
-         return EGL_FALSE;
-      }
-      ctx->DrawSurface = draw;
-      ctx->ReadSurface = read;
-      ctx->IsBound = EGL_TRUE;
-      draw->IsBound = EGL_TRUE;
-      read->IsBound = EGL_TRUE;
+   /* establish new bindings */
+   if (newCtx) {
+      if (newDraw)
+         newDraw->CurrentContext = newCtx;
+      if (newRead)
+         newRead->CurrentContext = newCtx;
+
+      newCtx->DrawSurface = newDraw;
+      newCtx->ReadSurface = newRead;
    }
 
-   _eglGlobal.CurrentContext = ctx;
+   /* an old context or surface is not orphaned if it is still bound */
+   if (*ctx == newCtx)
+      *ctx = NULL;
+   if (*draw == newDraw || *draw == newRead)
+      *draw = NULL;
+   if (*read == newDraw || *read == newRead)
+      *read = NULL;
 
    return EGL_TRUE;
 }
+
+
+/**
+ * Just a placeholder/demo function.  Drivers should override this.
+ */
+EGLBoolean
+_eglMakeCurrent(_EGLDriver *drv, _EGLDisplay *dpy, _EGLSurface *draw,
+                _EGLSurface *read, _EGLContext *ctx)
+{
+   return EGL_FALSE;
+}
+
+
+/**
+ * This is defined by the EGL_MESA_copy_context extension.
+ */
+EGLBoolean
+_eglCopyContextMESA(_EGLDriver *drv, EGLDisplay dpy, EGLContext source,
+                    EGLContext dest, EGLint mask)
+{
+   /* This function will always have to be overridden/implemented in the
+    * device driver.  If the driver is based on Mesa, use _mesa_copy_context().
+    */
+   return EGL_FALSE;
+}