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