diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..fac3abea4931782ed1dbfa1f7c8b0c29a51b60d7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,20 @@
+# Collection of examples about how to render to OpenGL textures from Vulkan
+
+The examples can be built with the following commands:
+
+```
+$ meson build/
+$ ninja -C build/
+```
+
+## test-glDrawVkImageNV
+
+```
+./build/test-glDrawVkImageNV
+```
+
+**NOTE**: The `glDrawVkImageNV` implementation on NVIDIA proprietary drivers seems to be broken when used from EGL with the X11 backend. This workaround seems to avoid the crash:
+
+```
+env -u DISPLAY ./build/test-glDrawVkImageNV
+```
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000000000000000000000000000000000000..8ccedeaca31087ef80feacfc0b57fa004052d99e
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,22 @@
+project('vulkan-render-to-texture', 'cpp',
+  default_options : ['warning_level=3', 'cpp_std=c++11' ],
+  version : '0.1')
+
+vulkan_dep = dependency('vulkan')
+egl_dep = dependency('egl')
+gl_dep = dependency('GL')
+
+deps = [ vulkan_dep, egl_dep, gl_dep ]
+
+executable('test-glDrawVkImageNV',
+           [
+             'test-glDrawVkImageNV.cpp',
+             'vulkan-render-to-opengl-texture-glDrawVkImageNV.cpp',
+             'external/Vulkan/examples/renderheadless/renderheadless.cpp',
+             'external/Vulkan/base/VulkanTools.cpp' ],
+           include_directories : [
+             'external/Vulkan/base',
+             'external/Vulkan/data',
+             'external/Vulkan/examples/renderheadless' ],
+           dependencies: deps,
+           install : false)
diff --git a/test-glDrawVkImageNV.cpp b/test-glDrawVkImageNV.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4eb8e697f4af9e40dc76658f3620b9491feb2419
--- /dev/null
+++ b/test-glDrawVkImageNV.cpp
@@ -0,0 +1,130 @@
+/*
+ * test-glDrawVkImageNV - Render to an OpenGL texture from Vulkan with glDrawVkImageNV
+ *
+ * Copyright 2021, Collabora Ltd
+ * Author: Antonio Ospite <antonio.ospite@collabora.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define GL_GLEXT_PROTOTYPES 1
+#include <GL/gl.h>
+#include <EGL/egl.h>
+
+#include "vulkan-render-to-opengl-texture-glDrawVkImageNV.h"
+
+int main (void)
+{
+  init ();
+
+  EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+
+  if (!eglInitialize(display, NULL, NULL)) {
+    switch(eglGetError()){
+      case EGL_BAD_DISPLAY:
+        fprintf(stderr, "Failed to initialize EGL Display: EGL_BAD_DISPLAY\n");
+        break;
+      case EGL_NOT_INITIALIZED:
+        fprintf(stderr, "Failed to initialize EGL Display: EGL_NOT_INITIALIZED\n");
+        break;
+      default:
+        fprintf(stderr, "Failed to initialize EGL Display: unknown erro\n");
+        break;
+    }
+
+    exit (EXIT_FAILURE);
+  }
+
+  eglBindAPI(EGL_OPENGL_API);
+
+  EGLint configAttribs[11] = {
+    EGL_BLUE_SIZE, 8,
+    EGL_GREEN_SIZE, 8,
+    EGL_RED_SIZE, 8,
+    EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
+    EGL_CONFORMANT, EGL_OPENGL_BIT,
+    EGL_NONE
+  };
+
+  EGLint numConfigs = 0;
+  EGLConfig eglConfig = nullptr;
+  if(!eglChooseConfig(display, configAttribs, &eglConfig, 1, &numConfigs)){
+      switch(eglGetError()){
+        case EGL_BAD_DISPLAY:
+          fprintf(stderr, "Failed to configure EGL Display: EGL_BAD_DISPLAY\n");
+          break;
+        case EGL_BAD_ATTRIBUTE:
+          fprintf(stderr, "Failed to configure EGL Display: EGL_BAD_ATTRIBUTE\n");
+          break;
+        case EGL_NOT_INITIALIZED:
+          fprintf(stderr, "Failed to configure EGL Display: EGL_NOT_INITIALIZED\n");
+          break;
+        case EGL_BAD_PARAMETER:
+          fprintf(stderr, "Failed to configure EGL Display: EGL_BAD_PARAMETER\n");
+          break;
+        default:
+          fprintf(stderr, "Failed to configure EGL Display: unknown error\n");
+          break;
+      }
+    exit (EXIT_FAILURE);
+  }
+
+  EGLContext context = eglCreateContext(display, eglConfig, EGL_NO_CONTEXT, NULL);
+  if (context == EGL_NO_CONTEXT) {
+    fprintf(stderr, "Failed to initialize EGL context: EGL_NO_CONTEXT\n");
+    exit (EXIT_FAILURE);
+  }
+
+  eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, context);
+
+  const GLubyte* vendor = glGetString(GL_VENDOR);
+  const GLubyte* renderer = glGetString(GL_RENDERER);
+  const GLubyte* version = glGetString(GL_VERSION);
+  const GLubyte* glslVer = glGetString(GL_SHADING_LANGUAGE_VERSION);
+  fprintf(stdout, "OpenGL Context: %s : %s (%s - GLSL: %s)\n", vendor, renderer, version, glslVer);
+
+  fprintf(stdout, "Info: Checking for required extensions:\n");
+  const char* NV_DRAW_VULKAN_EXT_STR = "GL_NV_draw_vulkan_image";
+  int numberOfExtensions;
+  glGetIntegerv(GL_NUM_EXTENSIONS, &numberOfExtensions);
+
+  for (int i = 0; i < numberOfExtensions; i++) {
+    const char * extStr = (const char*)glGetStringi(GL_EXTENSIONS, i);
+    if (strcmp(extStr, NV_DRAW_VULKAN_EXT_STR) == 0) {
+      fprintf(stdout, "Info: \t%s supported\n", NV_DRAW_VULKAN_EXT_STR);
+      fn_glDrawVkImageNV = (PFNGLDRAWVKIMAGENVPROC)eglGetProcAddress("glDrawVkImageNV");
+      fn_glWaitVkSemaphoreNV = (PFNGLWAITVKSEMAPHORENVPROC)eglGetProcAddress("glWaitVkSemaphoreNV");
+      fn_glSignalVkSemaphoreNV = (PFNGLSIGNALVKSEMAPHORENVPROC)eglGetProcAddress("glSignalVkSemaphoreNV");
+    }
+  }
+
+  if (fn_glDrawVkImageNV == NULL) {
+    fprintf(stderr, "Failed to find the %s extension!\n", NV_DRAW_VULKAN_EXT_STR);
+    exit(EXIT_FAILURE);
+  }
+
+  glEnable (GL_DEBUG_OUTPUT);
+  glDebugMessageCallback (debug_callback, NULL);
+
+  GLuint scene_texture;
+  glGenTextures (1, &scene_texture);
+  glBindTexture (GL_TEXTURE_2D, scene_texture);
+  glTexStorage2D (GL_TEXTURE_2D, 1, GL_RGB8, width, height);
+  glBindTexture (GL_TEXTURE_2D, 0);
+
+  uint32_t frame_count = 10;
+  while (frame_count-- > 0) {
+    glViewport (0, 0, width, height);
+
+    // Render scene into the target texture
+    draw_screen (scene_texture, width, height);
+  }
+
+  deinit ();
+
+  exit (EXIT_SUCCESS);
+}
diff --git a/vulkan-render-to-opengl-texture-glDrawVkImageNV.cpp b/vulkan-render-to-opengl-texture-glDrawVkImageNV.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1f691e56dae7c5744b342b57799d6a21d0f85a78
--- /dev/null
+++ b/vulkan-render-to-opengl-texture-glDrawVkImageNV.cpp
@@ -0,0 +1,163 @@
+/*
+ * vulkan-render-to-texture-glDrawVkImageNV - Render to an OpenGL texture from Vulkan with glDrawVkImageNV
+ *
+ * Copyright 2021, Collabora Ltd
+ * Author: Antonio Ospite <antonio.ospite@collabora.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "vulkan-render-to-opengl-texture-glDrawVkImageNV.h"
+#include "renderheadless.h"
+
+PFNGLDRAWVKIMAGENVPROC fn_glDrawVkImageNV;
+PFNGLWAITVKSEMAPHORENVPROC fn_glWaitVkSemaphoreNV;
+PFNGLSIGNALVKSEMAPHORENVPROC fn_glSignalVkSemaphoreNV;
+
+int width = 1024;
+int height = 1024;
+
+#define SCREENSHOT_MAX_FILENAME 256
+static char screenshot_filename[SCREENSHOT_MAX_FILENAME];
+static GLubyte *pixels = NULL;
+static const size_t format_nchannels = 3;
+
+static unsigned int nframes = 0;
+
+VulkanExample* vkExInstance = NULL;
+VkImage vkImage = NULL;
+VkSemaphore vkRenderDone = NULL;
+VkSemaphore vkPresentDone = NULL;
+
+/*
+ * Take screenshot with glReadPixels and save to a file in PPM format.
+ *
+ * - filename: file path to save to, without extension
+ * - width: screen width in pixels
+ * - height: screen height in pixels
+ * - pixels: intermediate buffer to avoid repeated mallocs across multiple calls.
+ */
+static void
+screenshot_ppm (const char *filename, int image_width,
+    int image_height, size_t nchannels, GLubyte ** image_pixels)
+{
+  int i, j;
+  size_t cur;
+  FILE *f = fopen (filename, "w");
+  fprintf (f, "P3\n%d %d\n%d\n", image_width, image_height, 255);
+  glReadPixels (0, 0, image_width, image_height, GL_RGB, GL_UNSIGNED_BYTE,
+      *image_pixels);
+  for (i = 0; i < image_height; i++) {
+    for (j = 0; j < image_width; j++) {
+      cur = nchannels * (i * image_width + j);
+      fprintf (f, "%3d %3d %3d ", (*image_pixels)[cur],
+          (*image_pixels)[cur + 1], (*image_pixels)[cur + 2]);
+    }
+    fprintf (f, "\n");
+  }
+  fclose (f);
+}
+
+void
+init (void)
+{
+  pixels = (GLubyte *)malloc(format_nchannels * sizeof (GLubyte) * width * height);
+  if (pixels == NULL) {
+      fprintf(stderr, "allocating pixels failed\n");
+      exit(EXIT_FAILURE);
+  }
+  vkExInstance = vulkan_example_init(width, height);
+
+  fprintf (stdout, "Info: %s\n", "init completed");
+}
+
+void
+deinit (void)
+{
+  vulkan_example_deinit(vkExInstance);
+  free (pixels);
+}
+
+static void
+draw_scene (void)
+{
+  auto vkData = vulkan_example_render(vkExInstance);
+  vkRenderDone = std::get<0>(vkData);
+  vkPresentDone = std::get<1>(vkData);
+  vkImage = std::get<2>(vkData);
+}
+
+void
+draw_screen (GLuint target_texture, int width, int height)
+{
+  //fprintf (stdout, "Info: Render start: %d\n", nframes);
+  draw_scene ();
+
+  /* Offscreen rendering framebuffer. */
+  static GLuint scene_fbo;
+  glGenFramebuffers (1, &scene_fbo);
+  glBindFramebuffer (GL_FRAMEBUFFER, scene_fbo);
+
+  glBindTexture (GL_TEXTURE_2D, target_texture);
+  glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+      target_texture, 0);
+
+  GLenum DrawBuffers[1] = { GL_COLOR_ATTACHMENT0 };
+  glDrawBuffers (1, DrawBuffers);
+
+  /* Sanity check. */
+  if (glCheckFramebufferStatus (GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+    fprintf (stderr, "glCheckFramebufferStatus() failed.");
+    return;
+  }
+
+  glClearColor(0.7f, 0.0f, 0.7f, 1.0f);
+  glClear(GL_COLOR_BUFFER_BIT);
+
+  fn_glWaitVkSemaphoreNV((GLuint64)vkRenderDone);
+
+  //fprintf (stdout, "Info: %s\n", "GPU Copy start");
+
+  fn_glDrawVkImageNV((GLuint64)vkImage, 0.0f,
+                  0.0f, 0.0f, width, height,  // dest coords
+                  0.0f,                    // z??
+                  0.0f, 0.0f, 1.0f, 1.0f); // source s, t coords
+
+  glFlush ();
+
+#if 1
+    snprintf (screenshot_filename, SCREENSHOT_MAX_FILENAME,
+              "frame-%05d.ppm", nframes);
+    screenshot_ppm (screenshot_filename, width, height, format_nchannels, &pixels);
+#endif
+
+  fn_glSignalVkSemaphoreNV((GLuint64)vkPresentDone);
+  //fprintf (stdout, "Info: %s\n", "GPU Copy end");
+
+  nframes++;
+}
+
+void
+error_callback (int error, const char *description)
+{
+  (void) error;
+  fprintf (stderr, "Error: %s\n", description);
+}
+
+void GLAPIENTRY
+debug_callback (GLenum source, GLenum type, GLuint id,
+    GLenum severity, GLsizei length,
+    const GLchar * message, const void *userParam)
+{
+  (void) source;
+  (void) id;
+  (void) length;
+  (void) userParam;
+  fprintf (stderr,
+      "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n",
+      (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""), type, severity,
+      message);
+}
diff --git a/vulkan-render-to-opengl-texture-glDrawVkImageNV.h b/vulkan-render-to-opengl-texture-glDrawVkImageNV.h
new file mode 100644
index 0000000000000000000000000000000000000000..ff9c46dc5ffb35b37af9bde62b5d86afd127b56a
--- /dev/null
+++ b/vulkan-render-to-opengl-texture-glDrawVkImageNV.h
@@ -0,0 +1,43 @@
+/*
+ * vulkan-render-to-texture-glDrawVkImageNV - Render to an OpenGL texture from Vulkan with glDrawVkImageNV
+ *
+ * Copyright 2021, Collabora Ltd
+ * Author: Antonio Ospite <antonio.ospite@collabora.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+#ifndef VULKAN_RENDER_TO_OPENGL_TEXTURE_H
+#define VULKAN_RENDER_TO_OPENGL_TEXTURE_H
+
+#define GL_GLEXT_PROTOTYPES 1
+#include <GL/gl.h>
+#include <GL/glext.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern PFNGLDRAWVKIMAGENVPROC fn_glDrawVkImageNV;
+extern PFNGLWAITVKSEMAPHORENVPROC fn_glWaitVkSemaphoreNV;
+extern PFNGLSIGNALVKSEMAPHORENVPROC fn_glSignalVkSemaphoreNV;
+
+extern int width;
+extern int height;
+
+void init (void);
+
+void deinit (void);
+
+void draw_screen (GLuint target_texture, int width, int height);
+
+void error_callback (int error, const char *description);
+
+void GLAPIENTRY debug_callback (GLenum source, GLenum type, GLuint id,
+    GLenum severity, GLsizei length,
+    const GLchar * message, const void *userParam);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif