| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #undef NDEBUG |
| |
| extern "C" { |
| #include <linux/virtio_gpu.h> |
| #include <virglrenderer.h> |
| #include <virgl_hw.h> |
| } |
| |
| #include <sys/uio.h> |
| |
| #include <dlfcn.h> |
| #include <algorithm> |
| #include <cassert> |
| #include <cerrno> |
| #include <cstdio> |
| #include <cstdlib> |
| #include <cstring> |
| #include <deque> |
| #include <map> |
| #include <mutex> |
| #include <string> |
| #include <vector> |
| |
| #include <EGL/egl.h> |
| #include <EGL/eglext.h> |
| #include <GLES/gl.h> |
| #include <GLES/glext.h> |
| #include <GLES3/gl31.h> |
| #include <GLES3/gl3ext.h> |
| |
| #include <drm/drm_fourcc.h> |
| |
| #include <OpenGLESDispatch/EGLDispatch.h> |
| #include <OpenGLESDispatch/GLESv1Dispatch.h> |
| #include <OpenGLESDispatch/GLESv3Dispatch.h> |
| |
| #include "OpenglRender/IOStream.h" |
| |
| #include "Context.h" |
| #include "EglConfig.h" |
| #include "EglContext.h" |
| #include "EglSurface.h" |
| #include "EglSync.h" |
| #include "Resource.h" |
| |
| #include <VirtioGpuCmd.h> |
| |
| // for debug only |
| #include <sys/syscall.h> |
| #include <unistd.h> |
| #define gettid() (int)syscall(__NR_gettid) |
| |
| #ifndef PAGE_SIZE |
| #define PAGE_SIZE 0x1000 |
| #endif |
| |
| #define MAX_CMDRESPBUF_SIZE (10 * PAGE_SIZE) |
| |
| #define ALIGN(A, B) (((A) + (B)-1) / (B) * (B)) |
| |
| // Enable passing scanout buffers as texture names to sdl2 backend |
| #define QEMU_HARDWARE_GL_INTEROP |
| |
| #ifdef QEMU_HARDWARE_GL_INTEROP |
| typedef GLenum (*PFNGLGETERROR)(void); |
| typedef void (*PFNGLBINDTEXTURE)(GLenum target, GLuint texture); |
| typedef void (*PFNGLGENTEXTURES)(GLsizei n, GLuint* textures); |
| typedef void (*PFNGLTEXPARAMETERI)(GLenum target, GLenum pname, GLint param); |
| typedef void (*PFNGLPIXELSTOREI)(GLenum pname, GLint param); |
| typedef void (*PFNGLTEXIMAGE2D)(GLenum target, GLint level, GLint internalformat, GLsizei width, |
| GLsizei height, GLint border, GLenum format, GLenum type, |
| const void* pixels); |
| static PFNGLBINDTEXTURE g_glBindTexture; |
| static PFNGLGENTEXTURES g_glGenTextures; |
| static PFNGLTEXPARAMETERI g_glTexParameteri; |
| static PFNGLPIXELSTOREI g_glPixelStorei; |
| static PFNGLTEXIMAGE2D g_glTexImage2D; |
| static virgl_renderer_gl_context g_ctx0_alt; |
| #endif |
| |
| // Global state |
| std::map<uint32_t, EglContext*> EglContext::map; |
| std::map<uint32_t, EglSurface*> EglSurface::map; |
| std::map<uint32_t, EglImage*> EglImage::map; |
| std::map<uint32_t, Resource*> Resource::map; |
| std::map<uint64_t, EglSync*> EglSync::map; |
| std::map<uint32_t, Context*> Context::map; |
| std::vector<EglConfig*> EglConfig::vec; |
| static virgl_renderer_callbacks* g_cb; |
| const EGLint EglConfig::kAttribs[]; |
| uint32_t EglContext::nextId = 1U; |
| uint32_t EglSurface::nextId = 1U; |
| uint32_t EglImage::nextId = 1U; |
| uint64_t EglSync::nextId = 1U; |
| static void* g_cookie; |
| |
| // Fence queue, must be thread safe |
| static std::mutex g_fence_deque_mutex; |
| static std::deque<int> g_fence_deque; |
| |
| // Other GPU context and state |
| static EGLSurface g_ctx0_surface; |
| static EGLContext g_ctx0_es1; |
| static EGLContext g_ctx0_es2; |
| static EGLDisplay g_dpy; |
| |
| // Last context receiving a command. Allows us to find the context a fence is |
| // being created for. Works around the poorly designed virgl interface. |
| static Context* g_last_submit_cmd_ctx; |
| |
| #ifdef OPENGL_DEBUG_PRINTOUT |
| |
| #include "emugl/common/logging.h" |
| |
| // For logging from the protocol decoders |
| |
| void default_logger(const char* fmt, ...) { |
| va_list ap; |
| va_start(ap, fmt); |
| vprintf(fmt, ap); |
| va_end(ap); |
| } |
| |
| emugl_logger_t emugl_cxt_logger = default_logger; |
| |
| #endif |
| |
| static void dump_global_state(void) { |
| printf("AVDVIRGLRENDERER GLOBAL STATE\n\n"); |
| |
| printf("Resources:\n"); |
| for (auto const& it : Resource::map) { |
| Resource const* res = it.second; |
| |
| printf( |
| " Resource %u: %ux%u 0x%x %p (%zub) t=%u b=%u d=%u a=%u l=%u " |
| "n=%u f=%u\n", |
| res->args.handle, res->args.width, res->args.height, res->args.format, |
| res->iov ? res->iov[0].iov_base : nullptr, res->iov ? res->iov[0].iov_len : 0, |
| res->args.target, res->args.bind, res->args.depth, res->args.array_size, |
| res->args.last_level, res->args.nr_samples, res->args.flags); |
| |
| for (auto const& it : res->context_map) { |
| Context const* ctx = it.second; |
| |
| printf(" Context %u, pid=%d, tid=%d\n", ctx->handle, ctx->pid, ctx->tid); |
| } |
| } |
| |
| printf("Contexts:\n"); |
| for (auto const& it : Context::map) { |
| Context const* ctx = it.second; |
| |
| printf(" Context %u: %s pid=%u tid=%u\n", ctx->handle, ctx->name.c_str(), ctx->pid, |
| ctx->tid); |
| |
| for (auto const& it : ctx->resource_map) { |
| Resource const* res = it.second; |
| |
| printf(" Resource %u\n", res->args.handle); |
| } |
| } |
| } |
| |
| static int sync_linear_to_iovec(Resource* res, uint64_t offset, const virgl_box* box) { |
| uint32_t bpp; |
| switch (res->args.format) { |
| case VIRGL_FORMAT_R8_UNORM: |
| bpp = 1U; |
| break; |
| case VIRGL_FORMAT_B5G6R5_UNORM: |
| bpp = 2U; |
| break; |
| default: |
| bpp = 4U; |
| break; |
| } |
| |
| if (box->x > res->args.width || box->y > res->args.height) |
| return 0; |
| if (box->w == 0U || box->h == 0U) |
| return 0; |
| uint32_t w = std::min(box->w, res->args.width - box->x); |
| uint32_t h = std::min(box->h, res->args.height - box->y); |
| uint32_t stride = ALIGN(res->args.width * bpp, 16U); |
| offset += box->y * stride + box->x * bpp; |
| size_t length = (h - 1U) * stride + w * bpp; |
| if (offset + length > res->linearSize) |
| return EINVAL; |
| |
| if (res->num_iovs > 1) { |
| const char* linear = static_cast<const char*>(res->linear); |
| for (uint32_t i = 0, iovOffset = 0U; length && i < res->num_iovs; i++) { |
| if (iovOffset + res->iov[i].iov_len > offset) { |
| char* iov_base = static_cast<char*>(res->iov[i].iov_base); |
| size_t copyLength = std::min(length, res->iov[i].iov_len); |
| memcpy(iov_base + offset - iovOffset, linear, copyLength); |
| linear += copyLength; |
| offset += copyLength; |
| length -= copyLength; |
| } |
| iovOffset += res->iov[i].iov_len; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int sync_iovec_to_linear(Resource* res, uint64_t offset, const virgl_box* box) { |
| uint32_t bpp; |
| switch (res->args.format) { |
| case VIRGL_FORMAT_R8_UNORM: |
| bpp = 1U; |
| break; |
| case VIRGL_FORMAT_B5G6R5_UNORM: |
| bpp = 2U; |
| break; |
| default: |
| bpp = 4U; |
| break; |
| } |
| |
| if (box->x > res->args.width || box->y > res->args.height) |
| return 0; |
| if (box->w == 0U || box->h == 0U) |
| return 0; |
| uint32_t w = std::min(box->w, res->args.width - box->x); |
| uint32_t h = std::min(box->h, res->args.height - box->y); |
| uint32_t stride = ALIGN(res->args.width * bpp, 16U); |
| offset += box->y * stride + box->x * bpp; |
| size_t length = (h - 1U) * stride + w * bpp; |
| if (offset + length > res->linearSize) |
| return EINVAL; |
| |
| if (res->num_iovs > 1) { |
| char* linear = static_cast<char*>(res->linear); |
| for (uint32_t i = 0, iovOffset = 0U; length && i < res->num_iovs; i++) { |
| if (iovOffset + res->iov[i].iov_len > offset) { |
| const char* iov_base = static_cast<const char*>(res->iov[i].iov_base); |
| size_t copyLength = std::min(length, res->iov[i].iov_len); |
| memcpy(linear, iov_base + offset - iovOffset, copyLength); |
| linear += copyLength; |
| offset += copyLength; |
| length -= copyLength; |
| } |
| iovOffset += res->iov[i].iov_len; |
| } |
| } |
| |
| return 0; |
| } |
| |
| // The below API was defined by virglrenderer 'master', but does not seem to |
| // be used by QEMU, so just ignore it for now.. |
| // |
| // virgl_renderer_get_rect |
| // virgl_renderer_get_fd_for_texture |
| // virgl_renderer_cleanup |
| // virgl_renderer_reset |
| // virgl_renderer_get_poll_fd |
| |
| int virgl_renderer_init(void* cookie, int flags, virgl_renderer_callbacks* cb) { |
| if (!cookie || !cb) |
| return EINVAL; |
| |
| if (flags != 0) |
| return ENOSYS; |
| |
| if (cb->version != 1) |
| return ENOSYS; |
| |
| #ifdef QEMU_HARDWARE_GL_INTEROP |
| // FIXME: If we just use "libGL.so" here, mesa's interception library returns |
| // stub dlsyms that do nothing at runtime, even after binding.. |
| void* handle = dlopen( |
| "/usr/lib/x86_64-linux-gnu/nvidia/" |
| "current/libGL.so.384.111", |
| RTLD_NOW); |
| assert(handle != nullptr); |
| g_glBindTexture = (PFNGLBINDTEXTURE)dlsym(handle, "glBindTexture"); |
| assert(g_glBindTexture != nullptr); |
| g_glGenTextures = (PFNGLGENTEXTURES)dlsym(handle, "glGenTextures"); |
| assert(g_glGenTextures != nullptr); |
| g_glTexParameteri = (PFNGLTEXPARAMETERI)dlsym(handle, "glTexParameteri"); |
| assert(g_glTexParameteri != nullptr); |
| g_glPixelStorei = (PFNGLPIXELSTOREI)dlsym(handle, "glPixelStorei"); |
| assert(g_glPixelStorei != nullptr); |
| g_glTexImage2D = (PFNGLTEXIMAGE2D)dlsym(handle, "glTexImage2D"); |
| assert(g_glTexImage2D != nullptr); |
| #endif |
| |
| if (!egl_dispatch_init()) |
| return ENOENT; |
| |
| if (!gles1_dispatch_init()) |
| return ENOENT; |
| |
| if (!gles3_dispatch_init()) |
| return ENOENT; |
| |
| g_dpy = s_egl.eglGetDisplay(EGL_DEFAULT_DISPLAY); |
| if (g_dpy == EGL_NO_DISPLAY) { |
| printf("Failed to open default EGL display\n"); |
| return ENOENT; |
| } |
| |
| if (!s_egl.eglInitialize(g_dpy, nullptr, nullptr)) { |
| printf("Failed to initialize EGL display\n"); |
| g_dpy = EGL_NO_DISPLAY; |
| return ENOENT; |
| } |
| |
| EGLint nConfigs; |
| if (!s_egl.eglGetConfigs(g_dpy, nullptr, 0, &nConfigs)) { |
| printf("Failed to retrieve number of EGL configs\n"); |
| s_egl.eglTerminate(g_dpy); |
| g_dpy = EGL_NO_DISPLAY; |
| return ENOENT; |
| } |
| |
| EGLConfig configs[nConfigs]; |
| if (!s_egl.eglGetConfigs(g_dpy, configs, nConfigs, &nConfigs)) { |
| printf("Failed to retrieve EGL configs\n"); |
| s_egl.eglTerminate(g_dpy); |
| g_dpy = EGL_NO_DISPLAY; |
| return ENOENT; |
| } |
| |
| // Our static analyzer sees the `new`ing of `config` below without any sort |
| // of attempt to free it, and warns about it. Normally, it would catch that |
| // we're pushing it into a vector in the constructor, but it hits an |
| // internal evaluation limit when trying to evaluate the loop inside of the |
| // ctor. So, it never gets to see that we escape our newly-allocated |
| // `config` instance. Silence the warning, since it's incorrect. |
| // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) |
| for (EGLint c = 0; c < nConfigs; c++) { |
| EGLint configId; |
| if (!s_egl.eglGetConfigAttrib(g_dpy, configs[c], EGL_CONFIG_ID, &configId)) { |
| printf("Failed to retrieve EGL config ID\n"); |
| s_egl.eglTerminate(g_dpy); |
| g_dpy = EGL_NO_DISPLAY; |
| return ENOENT; |
| } |
| EglConfig* config = |
| new (std::nothrow) EglConfig(g_dpy, configs[c], s_egl.eglGetConfigAttrib); |
| if (!config) |
| return ENOMEM; |
| } |
| |
| // clang-format off |
| EGLint const attrib_list[] = { |
| EGL_CONFORMANT, EGL_OPENGL_ES_BIT | EGL_OPENGL_ES3_BIT_KHR, |
| EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, |
| EGL_NONE, |
| }; |
| // clang-format on |
| EGLint num_config = 0; |
| EGLConfig config; |
| if (!s_egl.eglChooseConfig(g_dpy, attrib_list, &config, 1, &num_config) || num_config != 1) { |
| printf("Failed to select ES1 & ES3 capable EGL config\n"); |
| s_egl.eglTerminate(g_dpy); |
| g_dpy = EGL_NO_DISPLAY; |
| return ENOENT; |
| } |
| |
| // clang-format off |
| EGLint const pbuffer_attrib_list[] = { |
| EGL_WIDTH, 1, |
| EGL_HEIGHT, 1, |
| EGL_NONE |
| }; |
| // clang-format on |
| g_ctx0_surface = s_egl.eglCreatePbufferSurface(g_dpy, config, pbuffer_attrib_list); |
| if (!g_ctx0_surface) { |
| printf("Failed to create pbuffer surface for context 0\n"); |
| s_egl.eglTerminate(g_dpy); |
| g_dpy = EGL_NO_DISPLAY; |
| return ENOENT; |
| } |
| |
| // clang-format off |
| EGLint const es1_attrib_list[] = { |
| EGL_CONTEXT_CLIENT_VERSION, 1, |
| EGL_NONE |
| }; |
| // clang-format on |
| g_ctx0_es1 = s_egl.eglCreateContext(g_dpy, config, EGL_NO_CONTEXT, es1_attrib_list); |
| if (g_ctx0_es1 == EGL_NO_CONTEXT) { |
| printf("Failed to create ES1 context 0\n"); |
| s_egl.eglDestroySurface(g_dpy, g_ctx0_surface); |
| s_egl.eglTerminate(g_dpy); |
| g_dpy = EGL_NO_DISPLAY; |
| return ENOENT; |
| } |
| |
| // clang-format off |
| EGLint const es2_attrib_list[] = { |
| EGL_CONTEXT_CLIENT_VERSION, 3, // yes, 3 |
| EGL_NONE |
| }; |
| // clang-format on |
| g_ctx0_es2 = s_egl.eglCreateContext(g_dpy, config, EGL_NO_CONTEXT, es2_attrib_list); |
| if (g_ctx0_es2 == EGL_NO_CONTEXT) { |
| printf("Failed to create ES2 context 0\n"); |
| s_egl.eglDestroySurface(g_dpy, g_ctx0_surface); |
| s_egl.eglDestroyContext(g_dpy, g_ctx0_es1); |
| g_ctx0_es1 = EGL_NO_CONTEXT; |
| s_egl.eglTerminate(g_dpy); |
| g_dpy = EGL_NO_DISPLAY; |
| } |
| |
| #ifdef QEMU_HARDWARE_GL_INTEROP |
| // This is the hardware GPU context. In future, this code should probably |
| // be removed and SwiftShader be used for all presentation blits. |
| virgl_renderer_gl_ctx_param ctx_params = { |
| .major_ver = 3, |
| .minor_ver = 0, |
| }; |
| g_ctx0_alt = cb->create_gl_context(cookie, 0, &ctx_params); |
| if (!g_ctx0_alt) { |
| printf("Failed to create hardware GL context 0\n"); |
| s_egl.eglDestroySurface(g_dpy, g_ctx0_surface); |
| s_egl.eglDestroyContext(g_dpy, g_ctx0_es1); |
| g_ctx0_es1 = EGL_NO_CONTEXT; |
| s_egl.eglTerminate(g_dpy); |
| g_dpy = EGL_NO_DISPLAY; |
| } |
| |
| // Test we can actually make it current; otherwise, bail |
| if (cb->make_current(cookie, 0, g_ctx0_alt)) { |
| printf("Failed to make hardware GL context 0 current\n"); |
| cb->destroy_gl_context(cookie, g_ctx0_alt); |
| g_ctx0_alt = nullptr; |
| s_egl.eglDestroySurface(g_dpy, g_ctx0_surface); |
| s_egl.eglDestroyContext(g_dpy, g_ctx0_es1); |
| g_ctx0_es1 = EGL_NO_CONTEXT; |
| s_egl.eglTerminate(g_dpy); |
| g_dpy = EGL_NO_DISPLAY; |
| } |
| #endif |
| |
| EglContext::nextId = 1U; |
| g_cookie = cookie; |
| g_cb = cb; |
| return 0; |
| } |
| |
| void virgl_renderer_poll(void) { |
| std::lock_guard<std::mutex> lk(g_fence_deque_mutex); |
| for (auto fence : g_fence_deque) |
| g_cb->write_fence(g_cookie, fence); |
| g_fence_deque.clear(); |
| } |
| |
| void* virgl_renderer_get_cursor_data(uint32_t resource_id, uint32_t* width, uint32_t* height) { |
| if (!width || !height) |
| return nullptr; |
| |
| std::map<uint32_t, Resource*>::iterator it; |
| it = Resource::map.find(resource_id); |
| if (it == Resource::map.end()) |
| return nullptr; |
| |
| Resource* res = it->second; |
| if (res->args.bind != VIRGL_RES_BIND_CURSOR) |
| return nullptr; |
| |
| void* pixels = malloc(res->linearSize); |
| memcpy(pixels, res->linear, res->linearSize); |
| *height = res->args.height; |
| *width = res->args.width; |
| return pixels; |
| } |
| |
| // NOTE: This function is called from thread context. Do not touch anything |
| // without a mutex to protect it from concurrent access. Everything else in |
| // libvirglrenderer is designed to be single-threaded *only*. |
| |
| // Hack to serialize all calls into EGL or GLES functions due to bugs in |
| // swiftshader. This should be removed as soon as possible. |
| static std::mutex swiftshader_wa_mutex; |
| |
| static void process_cmd(Context* ctx, char* buf, size_t bufSize, int fence) { |
| VirtioGpuCmd* cmd_resp = reinterpret_cast<VirtioGpuCmd*>(ctx->cmd_resp->linear); |
| |
| IOStream stream(cmd_resp->buf, MAX_CMDRESPBUF_SIZE - sizeof(*cmd_resp)); |
| |
| { |
| std::lock_guard<std::mutex> lk(swiftshader_wa_mutex); |
| size_t decodedBytes; |
| |
| decodedBytes = ctx->render_control.decode(buf, bufSize, &stream, &ctx->checksum_calc); |
| bufSize -= decodedBytes; |
| buf += decodedBytes; |
| |
| decodedBytes = ctx->gles1.decode(buf, bufSize, &stream, &ctx->checksum_calc); |
| bufSize -= decodedBytes; |
| buf += decodedBytes; |
| |
| decodedBytes = ctx->gles3.decode(buf, bufSize, &stream, &ctx->checksum_calc); |
| bufSize -= decodedBytes; |
| buf += decodedBytes; |
| } |
| |
| assert(bufSize == 0); |
| |
| cmd_resp->cmdSize += stream.getFlushSize(); |
| |
| printf("(tid %d) ctx %d: cmd %u, size %zu, fence %d\n", gettid(), ctx->handle, cmd_resp->op, |
| cmd_resp->cmdSize - sizeof(*cmd_resp), fence); |
| if (cmd_resp->cmdSize - sizeof(*cmd_resp) > 0) { |
| printf("(tid %d) ", gettid()); |
| for (size_t i = 0; i < cmd_resp->cmdSize - sizeof(*cmd_resp); i++) { |
| printf("%.2x ", (unsigned char)cmd_resp->buf[i]); |
| } |
| printf("\n"); |
| } |
| |
| virgl_box box = { |
| .w = cmd_resp->cmdSize, |
| .h = 1, |
| }; |
| sync_linear_to_iovec(ctx->cmd_resp, 0, &box); |
| |
| { |
| std::lock_guard<std::mutex> lk(g_fence_deque_mutex); |
| g_fence_deque.push_back(fence); |
| } |
| } |
| |
| int virgl_renderer_submit_cmd(void* buffer, int ctx_id, int ndw) { |
| VirtioGpuCmd* cmd = static_cast<VirtioGpuCmd*>(buffer); |
| size_t bufSize = sizeof(uint32_t) * ndw; |
| |
| if (bufSize < sizeof(*cmd)) { |
| printf("bad buffer size, bufSize=%zu, ctx=%d\n", bufSize, ctx_id); |
| return -1; |
| } |
| |
| printf("ctx %d: cmd %u, size %zu\n", ctx_id, cmd->op, cmd->cmdSize - sizeof(*cmd)); |
| |
| for (size_t i = 0; i < bufSize - sizeof(*cmd); i++) { |
| printf("%.2x ", (unsigned char)cmd->buf[i]); |
| } |
| printf("\n"); |
| |
| if (cmd->cmdSize < bufSize) { |
| printf("ignoring short command, cmdSize=%u, bufSize=%zu\n", cmd->cmdSize, bufSize); |
| return 0; |
| } |
| |
| if (cmd->cmdSize > bufSize) { |
| printf("command would overflow buffer, cmdSize=%u, bufSize=%zu\n", cmd->cmdSize, bufSize); |
| return -1; |
| } |
| |
| std::map<uint32_t, Context*>::iterator it; |
| it = Context::map.find((uint32_t)ctx_id); |
| if (it == Context::map.end()) { |
| printf("command submit from invalid context %d, ignoring\n", ctx_id); |
| return 0; |
| } |
| |
| Context* ctx = it->second; |
| |
| // When the context is created, the remote side should send a test command |
| // (op == 0) which we use to set up our link to this context's 'response |
| // buffer'. Only apps using EGL or GLES have this. Gralloc contexts will |
| // never hit this path because they do not submit 3D commands. |
| if (cmd->op == 0) { |
| std::map<uint32_t, Resource*>::iterator it; |
| it = Resource::map.find(*(uint32_t*)cmd->buf); |
| if (it != Resource::map.end()) { |
| Resource* res = it->second; |
| size_t cmdRespBufSize = 0U; |
| for (size_t i = 0; i < res->num_iovs; i++) |
| cmdRespBufSize += res->iov[i].iov_len; |
| if (cmdRespBufSize == MAX_CMDRESPBUF_SIZE) |
| ctx->cmd_resp = res; |
| } |
| } |
| |
| if (!ctx->cmd_resp) { |
| printf("context command response page not set up, ctx=%d\n", ctx_id); |
| return -1; |
| } |
| |
| VirtioGpuCmd* cmd_resp = reinterpret_cast<VirtioGpuCmd*>(ctx->cmd_resp->linear); |
| |
| // We can configure bits of the response now. The size, and any message, will |
| // be updated later. This must be done even for the dummy 'op == 0' command. |
| cmd_resp->op = cmd->op; |
| cmd_resp->cmdSize = sizeof(*cmd_resp); |
| |
| if (cmd->op == 0) { |
| // Send back a no-op response, just to keep the protocol in check |
| virgl_box box = { |
| .w = cmd_resp->cmdSize, |
| .h = 1, |
| }; |
| sync_linear_to_iovec(ctx->cmd_resp, 0, &box); |
| } else { |
| // If the rcSetPuid command was already processed, this command will be |
| // processed by another thread. If not, the command data will be copied |
| // here and responded to when ctx->setFence() is called later. |
| ctx->submitCommand(buffer, bufSize); |
| } |
| |
| g_last_submit_cmd_ctx = ctx; |
| return 0; |
| } |
| |
| void virgl_renderer_get_cap_set(uint32_t set, uint32_t* max_ver, uint32_t* max_size) { |
| if (!max_ver || !max_size) |
| return; |
| |
| printf("Request for caps version %u\n", set); |
| |
| switch (set) { |
| case 1: |
| *max_ver = 1; |
| *max_size = sizeof(virgl_caps_v1); |
| break; |
| case 2: |
| *max_ver = 2; |
| *max_size = sizeof(virgl_caps_v2); |
| break; |
| default: |
| *max_ver = 0; |
| *max_size = 0; |
| break; |
| } |
| } |
| |
| void virgl_renderer_fill_caps(uint32_t set, uint32_t, void* caps_) { |
| union virgl_caps* caps = static_cast<union virgl_caps*>(caps_); |
| EGLSurface old_read_surface, old_draw_surface; |
| GLfloat range[2] = { 0.0f, 0.0f }; |
| bool fill_caps_v2 = false; |
| EGLContext old_context; |
| GLint max = 0; |
| |
| if (!caps) |
| return; |
| |
| // We don't need to handle caps yet, because our guest driver's features |
| // should be as close as possible to the host driver's. But maybe some day |
| // we'll support gallium shaders and the virgl control stream, so it seems |
| // like a good idea to set up the driver caps correctly.. |
| |
| // If this is broken, nothing will work properly |
| old_read_surface = s_egl.eglGetCurrentSurface(EGL_READ); |
| old_draw_surface = s_egl.eglGetCurrentSurface(EGL_DRAW); |
| old_context = s_egl.eglGetCurrentContext(); |
| if (!s_egl.eglMakeCurrent(g_dpy, g_ctx0_surface, g_ctx0_surface, g_ctx0_es1)) { |
| printf("Failed to make ES1 context current\n"); |
| return; |
| } |
| |
| // Don't validate 'version' because it looks like this was misdesigned |
| // upstream and won't be set; instead, 'set' was bumped from 1->2. |
| |
| switch (set) { |
| case 0: |
| case 1: |
| memset(caps, 0, sizeof(virgl_caps_v1)); |
| caps->max_version = 1; |
| break; |
| case 2: |
| memset(caps, 0, sizeof(virgl_caps_v2)); |
| caps->max_version = 2; |
| fill_caps_v2 = true; |
| break; |
| default: |
| caps->max_version = 0; |
| return; |
| } |
| |
| if (fill_caps_v2) { |
| printf("Will probe and fill caps version 2.\n"); |
| } |
| |
| // Formats supported for textures |
| |
| caps->v1.sampler.bitmask[0] = (1 << (VIRGL_FORMAT_B8G8R8A8_UNORM - (0 * 32))) | |
| (1 << (VIRGL_FORMAT_B5G6R5_UNORM - (0 * 32))); |
| caps->v1.sampler.bitmask[2] = (1 << (VIRGL_FORMAT_R8G8B8A8_UNORM - (2 * 32))); |
| caps->v1.sampler.bitmask[4] = (1 << (VIRGL_FORMAT_R8G8B8X8_UNORM - (4 * 32))); |
| |
| // Formats supported for rendering |
| |
| caps->v1.render.bitmask[0] = (1 << (VIRGL_FORMAT_B8G8R8A8_UNORM - (0 * 32))) | |
| (1 << (VIRGL_FORMAT_B5G6R5_UNORM - (0 * 32))); |
| caps->v1.render.bitmask[2] = (1 << (VIRGL_FORMAT_R8G8B8A8_UNORM - (2 * 32))); |
| caps->v1.render.bitmask[4] = (1 << (VIRGL_FORMAT_R8G8B8X8_UNORM - (4 * 32))); |
| |
| // Could parse s_gles1.glGetString(GL_SHADING_LANGUAGE_VERSION, ...)? |
| caps->v1.glsl_level = 300; // OpenGL ES GLSL 3.00 |
| |
| // Call with any API (v1, v3) bound |
| |
| caps->v1.max_viewports = 1; |
| |
| s_gles1.glGetIntegerv(GL_MAX_DRAW_BUFFERS_EXT, &max); |
| caps->v1.max_render_targets = max; |
| |
| s_gles1.glGetIntegerv(GL_MAX_SAMPLES_EXT, &max); |
| caps->v1.max_samples = max; |
| |
| if (fill_caps_v2) { |
| s_gles1.glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, range); |
| caps->v2.min_aliased_point_size = range[0]; |
| caps->v2.max_aliased_point_size = range[1]; |
| |
| s_gles1.glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, range); |
| caps->v2.min_aliased_line_width = range[0]; |
| caps->v2.max_aliased_line_width = range[1]; |
| |
| // An extension, but everybody has it |
| s_gles1.glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &max); |
| caps->v2.max_vertex_attribs = max; |
| |
| // Call with ES 1.0 bound *only* |
| |
| s_gles1.glGetFloatv(GL_SMOOTH_POINT_SIZE_RANGE, range); |
| caps->v2.min_smooth_point_size = range[0]; |
| caps->v2.max_smooth_point_size = range[1]; |
| |
| s_gles1.glGetFloatv(GL_SMOOTH_LINE_WIDTH_RANGE, range); |
| caps->v2.min_smooth_line_width = range[0]; |
| caps->v2.max_smooth_line_width = range[1]; |
| } |
| |
| if (!s_egl.eglMakeCurrent(g_dpy, g_ctx0_surface, g_ctx0_surface, g_ctx0_es2)) { |
| s_egl.eglMakeCurrent(g_dpy, old_draw_surface, old_read_surface, old_context); |
| printf("Failed to make ES3 context current\n"); |
| return; |
| } |
| |
| // Call with ES 3.0 bound *only* |
| |
| caps->v1.bset.primitive_restart = 1; |
| caps->v1.bset.seamless_cube_map = 1; |
| caps->v1.bset.occlusion_query = 1; |
| caps->v1.bset.instanceid = 1; |
| caps->v1.bset.ubo = 1; |
| |
| s_gles1.glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &max); |
| caps->v1.max_texture_array_layers = max; |
| |
| s_gles1.glGetIntegerv(GL_MAX_VERTEX_UNIFORM_BLOCKS, &max); |
| caps->v1.max_uniform_blocks = max + 1; |
| |
| if (fill_caps_v2) { |
| s_gles1.glGetFloatv(GL_MAX_TEXTURE_LOD_BIAS, &caps->v2.max_texture_lod_bias); |
| |
| s_gles1.glGetIntegerv(GL_MAX_VERTEX_OUTPUT_COMPONENTS, &max); |
| caps->v2.max_vertex_outputs = max / 4; |
| |
| s_gles1.glGetIntegerv(GL_MIN_PROGRAM_TEXEL_OFFSET, &caps->v2.min_texel_offset); |
| s_gles1.glGetIntegerv(GL_MAX_PROGRAM_TEXEL_OFFSET, &caps->v2.max_texel_offset); |
| |
| s_gles1.glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &max); |
| caps->v2.uniform_buffer_offset_alignment = max; |
| } |
| |
| // ES 2.0 extensions (fixme) |
| |
| // Gallium compatibility; not usable currently. |
| caps->v1.prim_mask = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6); |
| |
| if (!s_egl.eglMakeCurrent(g_dpy, old_draw_surface, old_read_surface, old_context)) { |
| printf("Failed to make no context current\n"); |
| } |
| } |
| |
| int virgl_renderer_create_fence(int client_fence_id, uint32_t cmd_type) { |
| switch (cmd_type) { |
| case VIRTIO_GPU_CMD_SUBMIT_3D: |
| if (g_last_submit_cmd_ctx) { |
| g_last_submit_cmd_ctx->setFence(client_fence_id); |
| break; |
| } |
| [[fallthrough]]; |
| default: { |
| std::lock_guard<std::mutex> lk(g_fence_deque_mutex); |
| g_fence_deque.push_back(client_fence_id); |
| break; |
| } |
| } |
| return 0; |
| } |
| |
| void virgl_renderer_force_ctx_0(void) { |
| #ifdef QEMU_HARDWARE_GL_INTEROP |
| if (!g_ctx0_alt) |
| return; |
| |
| if (g_cb->make_current(g_cookie, 0, g_ctx0_alt)) { |
| printf("Failed to make hardware GL context 0 current\n"); |
| g_cb->destroy_gl_context(g_cookie, g_ctx0_alt); |
| g_ctx0_alt = nullptr; |
| } |
| #endif |
| } |
| |
| int virgl_renderer_resource_create(virgl_renderer_resource_create_args* args, iovec* iov, |
| uint32_t num_iovs) { |
| if (!args) |
| return EINVAL; |
| |
| if (args->bind == VIRGL_RES_BIND_CURSOR) { |
| // Enforce limitation of current virtio-gpu-3d implementation |
| if (args->width != 64 || args->height != 64 || args->format != VIRGL_FORMAT_B8G8R8A8_UNORM) |
| return EINVAL; |
| } |
| |
| assert(!Resource::map.count(args->handle) && "Can't insert same resource twice!"); |
| |
| Resource* res = new (std::nothrow) Resource(args, num_iovs, iov); |
| if (!res) |
| return ENOMEM; |
| |
| printf("Creating Resource %u (num_iovs=%u)\n", args->handle, num_iovs); |
| return 0; |
| } |
| |
| void virgl_renderer_resource_unref(uint32_t res_handle) { |
| std::map<uint32_t, Resource*>::iterator it; |
| |
| it = Resource::map.find(res_handle); |
| if (it == Resource::map.end()) |
| return; |
| |
| Resource* res = it->second; |
| |
| for (auto const& it : Context::map) { |
| Context const* ctx = it.second; |
| |
| virgl_renderer_ctx_detach_resource(ctx->handle, res->args.handle); |
| } |
| |
| assert(res->context_map.empty() && "Deleted resource was associated with contexts"); |
| |
| printf("Deleting Resource %u\n", res_handle); |
| delete res; |
| } |
| |
| int virgl_renderer_resource_attach_iov(int res_handle, iovec* iov, int num_iovs) { |
| std::map<uint32_t, Resource*>::iterator it; |
| |
| it = Resource::map.find((uint32_t)res_handle); |
| if (it == Resource::map.end()) |
| return ENOENT; |
| |
| Resource* res = it->second; |
| |
| if (!res->iov) { |
| printf( |
| "Attaching backing store for Resource %d " |
| "(num_iovs=%d)\n", |
| res_handle, num_iovs); |
| |
| res->num_iovs = num_iovs; |
| res->iov = iov; |
| |
| res->reallocLinear(); |
| |
| // Assumes that when resources are attached, they contain junk, and we |
| // don't need to synchronize with the linear buffer |
| } |
| |
| return 0; |
| } |
| |
| void virgl_renderer_resource_detach_iov(int res_handle, iovec** iov, int* num_iovs) { |
| std::map<uint32_t, Resource*>::iterator it; |
| |
| it = Resource::map.find((uint32_t)res_handle); |
| if (it == Resource::map.end()) |
| return; |
| |
| Resource* res = it->second; |
| |
| printf("Detaching backing store for Resource %d\n", res_handle); |
| |
| // Synchronize our linear buffer, if any, with the iovec that we are about |
| // to give up. Most likely this is not required, but it seems cleaner. |
| virgl_box box = { |
| .w = res->args.width, |
| .h = res->args.height, |
| }; |
| sync_linear_to_iovec(res, 0, &box); |
| |
| if (num_iovs) |
| *num_iovs = res->num_iovs; |
| res->num_iovs = 0U; |
| |
| if (iov) |
| *iov = res->iov; |
| res->iov = nullptr; |
| |
| res->reallocLinear(); |
| } |
| |
| int virgl_renderer_resource_get_info(int res_handle, virgl_renderer_resource_info* info) { |
| if (!info) |
| return EINVAL; |
| |
| std::map<uint32_t, Resource*>::iterator it; |
| |
| it = Resource::map.find((uint32_t)res_handle); |
| if (it == Resource::map.end()) |
| return ENOENT; |
| |
| Resource* res = it->second; |
| |
| uint32_t bpp = 4U; |
| switch (res->args.format) { |
| case VIRGL_FORMAT_B8G8R8A8_UNORM: |
| info->drm_fourcc = DRM_FORMAT_BGRA8888; |
| break; |
| case VIRGL_FORMAT_B5G6R5_UNORM: |
| info->drm_fourcc = DRM_FORMAT_BGR565; |
| bpp = 2U; |
| break; |
| case VIRGL_FORMAT_R8G8B8A8_UNORM: |
| info->drm_fourcc = DRM_FORMAT_RGBA8888; |
| break; |
| case VIRGL_FORMAT_R8G8B8X8_UNORM: |
| info->drm_fourcc = DRM_FORMAT_RGBX8888; |
| break; |
| default: |
| return EINVAL; |
| } |
| |
| #ifdef QEMU_HARDWARE_GL_INTEROP |
| GLenum type = GL_UNSIGNED_BYTE; |
| GLenum format = GL_RGBA; |
| switch (res->args.format) { |
| case VIRGL_FORMAT_B8G8R8A8_UNORM: |
| format = 0x80E1; // GL_BGRA |
| break; |
| case VIRGL_FORMAT_B5G6R5_UNORM: |
| type = GL_UNSIGNED_SHORT_5_6_5; |
| format = GL_RGB; |
| break; |
| case VIRGL_FORMAT_R8G8B8X8_UNORM: |
| format = GL_RGB; |
| break; |
| } |
| |
| if (!res->tex_id) { |
| g_glGenTextures(1, &res->tex_id); |
| g_glBindTexture(GL_TEXTURE_2D, res->tex_id); |
| g_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| g_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| g_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| g_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| } else { |
| g_glBindTexture(GL_TEXTURE_2D, res->tex_id); |
| } |
| |
| g_glPixelStorei(GL_UNPACK_ROW_LENGTH, ALIGN(res->args.width, 16)); |
| g_glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, res->args.width, res->args.height, 0, format, type, |
| res->linear); |
| #endif |
| |
| info->stride = ALIGN(res->args.width * bpp, 16U); |
| info->virgl_format = res->args.format; |
| info->handle = res->args.handle; |
| info->height = res->args.height; |
| info->width = res->args.width; |
| info->depth = res->args.depth; |
| info->flags = res->args.flags; |
| info->tex_id = res->tex_id; |
| |
| printf("Scanning out Resource %d\n", res_handle); |
| dump_global_state(); |
| |
| return 0; |
| } |
| |
| int virgl_renderer_context_create(uint32_t handle, uint32_t nlen, const char* name) { |
| assert(!Context::map.count(handle) && "Can't insert same context twice!"); |
| |
| Context* ctx = new (std::nothrow) Context(handle, name, nlen, process_cmd, g_dpy); |
| if (!ctx) |
| return ENOMEM; |
| |
| printf("Creating Context %u (%.*s)\n", handle, (int)nlen, name); |
| return 0; |
| } |
| |
| void virgl_renderer_context_destroy(uint32_t handle) { |
| std::map<uint32_t, Context*>::iterator it; |
| it = Context::map.find(handle); |
| if (it == Context::map.end()) |
| return; |
| |
| Context* ctx = it->second; |
| printf("Destroying Context %u\n", handle); |
| delete ctx; |
| } |
| |
| int virgl_renderer_transfer_read_iov(uint32_t handle, uint32_t, uint32_t, uint32_t, uint32_t, |
| virgl_box* box, uint64_t offset, iovec*, int) { |
| // stride, layer_stride and level are not set by minigbm, so don't try to |
| // validate them right now. iov and iovec_cnt are always passed as nullptr |
| // and 0 by qemu, so ignore those too |
| |
| std::map<uint32_t, Resource*>::iterator it; |
| it = Resource::map.find((uint32_t)handle); |
| if (it == Resource::map.end()) |
| return EINVAL; |
| |
| return sync_linear_to_iovec(it->second, offset, box); |
| } |
| |
| int virgl_renderer_transfer_write_iov(uint32_t handle, uint32_t, int, uint32_t, uint32_t, |
| virgl_box* box, uint64_t offset, iovec*, unsigned int) { |
| // stride, layer_stride and level are not set by minigbm, so don't try to |
| // validate them right now. iov and iovec_cnt are always passed as nullptr |
| // and 0 by qemu, so ignore those too |
| |
| std::map<uint32_t, Resource*>::iterator it; |
| it = Resource::map.find((uint32_t)handle); |
| if (it == Resource::map.end()) |
| return EINVAL; |
| |
| return sync_iovec_to_linear(it->second, offset, box); |
| } |
| |
| void virgl_renderer_ctx_attach_resource(int ctx_id, int res_handle) { |
| std::map<uint32_t, Context*>::iterator ctx_it; |
| |
| ctx_it = Context::map.find((uint32_t)ctx_id); |
| if (ctx_it == Context::map.end()) |
| return; |
| |
| Context* ctx = ctx_it->second; |
| |
| assert(!ctx->resource_map.count((uint32_t)res_handle) && |
| "Can't attach resource to context twice!"); |
| |
| std::map<uint32_t, Resource*>::iterator res_it; |
| |
| res_it = Resource::map.find((uint32_t)res_handle); |
| if (res_it == Resource::map.end()) |
| return; |
| |
| Resource* res = res_it->second; |
| |
| printf("Attaching Resource %d to Context %d\n", res_handle, ctx_id); |
| res->context_map.emplace((uint32_t)ctx_id, ctx); |
| ctx->resource_map.emplace((uint32_t)res_handle, res); |
| } |
| |
| void virgl_renderer_ctx_detach_resource(int ctx_id, int res_handle) { |
| std::map<uint32_t, Context*>::iterator ctx_it; |
| |
| ctx_it = Context::map.find((uint32_t)ctx_id); |
| if (ctx_it == Context::map.end()) |
| return; |
| |
| Context* ctx = ctx_it->second; |
| |
| std::map<uint32_t, Resource*>::iterator res_it; |
| |
| res_it = ctx->resource_map.find((uint32_t)res_handle); |
| if (res_it == ctx->resource_map.end()) |
| return; |
| |
| Resource* res = res_it->second; |
| |
| ctx_it = res->context_map.find((uint32_t)ctx_id); |
| if (ctx_it == res->context_map.end()) |
| return; |
| |
| printf("Detaching Resource %d from Context %d\n", res_handle, ctx_id); |
| if (ctx->cmd_resp && ctx->cmd_resp->args.handle == (uint32_t)res_handle) |
| ctx->cmd_resp = nullptr; |
| ctx->resource_map.erase(res_it); |
| res->context_map.erase(ctx_it); |
| } |