blob: a089489f87514ef4a34f63fe355adcb2972a226f [file] [log] [blame]
/*
* 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 <virgl/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;
}
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;
}
// Fall through
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);
}