/* * This file is part of libplacebo. * * libplacebo is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * libplacebo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with libplacebo. If not, see . */ #include "gpu.h" #include "formats.h" #include "utils.h" #ifdef PL_HAVE_UNIX #include #include #endif void gl_tex_destroy(pl_gpu gpu, pl_tex tex) { const gl_funcs *gl = gl_funcs_get(gpu); if (!MAKE_CURRENT()) { PL_ERR(gpu, "Failed uninitializing texture, leaking resources!"); return; } struct pl_tex_gl *tex_gl = PL_PRIV(tex); if (tex_gl->fbo && !tex_gl->wrapped_fb) gl->DeleteFramebuffers(1, &tex_gl->fbo); if (tex_gl->image) { struct pl_gl *p = PL_PRIV(gpu); eglDestroyImageKHR(p->egl_dpy, tex_gl->image); } if (!tex_gl->wrapped_tex) gl->DeleteTextures(1, &tex_gl->texture); #ifdef PL_HAVE_UNIX if (tex_gl->fd != -1) close(tex_gl->fd); #endif gl_check_err(gpu, "gl_tex_destroy"); RELEASE_CURRENT(); pl_free((void *) tex); } static GLbitfield tex_barrier(pl_tex tex) { GLbitfield barrier = 0; const struct pl_tex_params *params = &tex->params; if (params->sampleable) barrier |= GL_TEXTURE_FETCH_BARRIER_BIT; if (params->renderable || params->blit_src || params->blit_dst) barrier |= GL_FRAMEBUFFER_BARRIER_BIT; if (params->storable) barrier |= GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; if (params->host_writable || params->host_readable) barrier |= GL_TEXTURE_UPDATE_BARRIER_BIT; return barrier; } #define ADD_ATTRIB(name, value) \ do { \ assert(num_attribs + 3 < PL_ARRAY_SIZE(attribs)); \ attribs[num_attribs++] = (name); \ attribs[num_attribs++] = (value); \ } while (0) #define ADD_DMABUF_PLANE_ATTRIBS(plane, fd, offset, stride) \ do { \ ADD_ATTRIB(EGL_DMA_BUF_PLANE ## plane ## _FD_EXT, \ fd); \ ADD_ATTRIB(EGL_DMA_BUF_PLANE ## plane ## _OFFSET_EXT, \ offset); \ ADD_ATTRIB(EGL_DMA_BUF_PLANE ## plane ## _PITCH_EXT, \ stride); \ } while (0) #define ADD_DMABUF_PLANE_MODIFIERS(plane, mod) \ do { \ ADD_ATTRIB(EGL_DMA_BUF_PLANE ## plane ## _MODIFIER_LO_EXT, \ (uint32_t) ((mod) & 0xFFFFFFFFlu)); \ ADD_ATTRIB(EGL_DMA_BUF_PLANE ## plane ## _MODIFIER_HI_EXT, \ (uint32_t) (((mod) >> 32u) & 0xFFFFFFFFlu)); \ } while (0) static bool gl_tex_import(pl_gpu gpu, enum pl_handle_type handle_type, const struct pl_shared_mem *shared_mem, struct pl_tex_t *tex) { const gl_funcs *gl = gl_funcs_get(gpu); struct pl_gl *p = PL_PRIV(gpu); if (!MAKE_CURRENT()) return false; struct pl_tex_gl *tex_gl = PL_PRIV(tex); const struct pl_tex_params *params = &tex->params; int attribs[20] = {}; int num_attribs = 0; ADD_ATTRIB(EGL_WIDTH, params->w); ADD_ATTRIB(EGL_HEIGHT, params->h); switch (handle_type) { #ifdef PL_HAVE_UNIX case PL_HANDLE_DMA_BUF: if (shared_mem->handle.fd == -1) { PL_ERR(gpu, "%s: invalid fd", __func__); goto error; } tex_gl->fd = dup(shared_mem->handle.fd); if (tex_gl->fd == -1) { PL_ERR(gpu, "%s: cannot duplicate fd %d for importing: %s", __func__, shared_mem->handle.fd, strerror(errno)); goto error; } ADD_ATTRIB(EGL_LINUX_DRM_FOURCC_EXT, params->format->fourcc); ADD_DMABUF_PLANE_ATTRIBS(0, tex_gl->fd, shared_mem->offset, PL_DEF(shared_mem->stride_w, params->w)); if (p->has_modifiers) ADD_DMABUF_PLANE_MODIFIERS(0, shared_mem->drm_format_mod); attribs[num_attribs] = EGL_NONE; // EGL_LINUX_DMA_BUF_EXT requires EGL_NO_CONTEXT tex_gl->image = eglCreateImageKHR(p->egl_dpy, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer) NULL, attribs); break; #else // !PL_HAVE_UNIX case PL_HANDLE_DMA_BUF: pl_unreachable(); #endif case PL_HANDLE_WIN32: case PL_HANDLE_WIN32_KMT: case PL_HANDLE_HOST_PTR: case PL_HANDLE_FD: case PL_HANDLE_MTL_TEX: case PL_HANDLE_IOSURFACE: pl_unreachable(); } if (!egl_check_err(gpu, "eglCreateImageKHR") || !tex_gl->image) goto error; // tex_gl->image should be already bound if (p->has_egl_storage) { gl->EGLImageTargetTexStorageEXT(GL_TEXTURE_2D, tex_gl->image, NULL); } else { gl->EGLImageTargetTexture2DOES(GL_TEXTURE_2D, tex_gl->image); } if (!egl_check_err(gpu, "EGLImageTargetTexture2DOES")) goto error; RELEASE_CURRENT(); return true; error: PL_ERR(gpu, "Failed importing GL texture!"); RELEASE_CURRENT(); return false; } static EGLenum egl_from_gl_target(pl_gpu gpu, int target) { switch(target) { case GL_TEXTURE_2D: return EGL_GL_TEXTURE_2D; case GL_TEXTURE_3D: return EGL_GL_TEXTURE_3D; default: PL_ERR(gpu, "%s: unsupported texture target 0x%x", __func__, target); return 0; } } static bool gl_tex_export(pl_gpu gpu, enum pl_handle_type handle_type, bool preserved, struct pl_tex_t *tex) { struct pl_tex_gl *tex_gl = PL_PRIV(tex); struct pl_gl *p = PL_PRIV(gpu); EGLenum egltarget = egl_from_gl_target(gpu, tex_gl->target); if (!egltarget) goto error; int attribs[] = { EGL_IMAGE_PRESERVED, preserved, EGL_NONE, }; // We assume that tex_gl->texture is already bound tex_gl->image = eglCreateImageKHR(p->egl_dpy, p->egl_ctx, egltarget, (EGLClientBuffer) (uintptr_t) tex_gl->texture, attribs); if (!egl_check_err(gpu, "eglCreateImageKHR") || !tex_gl->image) goto error; switch (handle_type) { #ifdef PL_HAVE_UNIX case PL_HANDLE_DMA_BUF: { int fourcc = 0; int num_planes = 0; EGLuint64KHR modifier = 0; bool ok; ok = eglExportDMABUFImageQueryMESA(p->egl_dpy, tex_gl->image, &fourcc, &num_planes, &modifier); if (!egl_check_err(gpu, "eglExportDMABUFImageQueryMESA") || !ok) goto error; if (fourcc != tex->params.format->fourcc) { PL_ERR(gpu, "Exported DRM format %s does not match fourcc of " "specified pl_fmt %s? Please open a bug.", PRINT_FOURCC(fourcc), PRINT_FOURCC(tex->params.format->fourcc)); goto error; } if (num_planes != 1) { PL_ERR(gpu, "Unsupported number of planes: %d", num_planes); goto error; } int offset = 0, stride = 0; ok = eglExportDMABUFImageMESA(p->egl_dpy, tex_gl->image, &tex_gl->fd, &stride, &offset); if (!egl_check_err(gpu, "eglExportDMABUFImageMesa") || !ok) goto error; off_t fdsize = lseek(tex_gl->fd, 0, SEEK_END); off_t err = fdsize > 0 && lseek(tex_gl->fd, 0, SEEK_SET); if (fdsize <= 0 || err < 0) { PL_ERR(gpu, "Failed querying FD size: %s", strerror(errno)); goto error; } tex->shared_mem = (struct pl_shared_mem) { .handle.fd = tex_gl->fd, .size = fdsize, .offset = offset, .drm_format_mod = modifier, .stride_w = stride, }; break; } #else // !PL_HAVE_UNIX case PL_HANDLE_DMA_BUF: pl_unreachable(); #endif case PL_HANDLE_WIN32: case PL_HANDLE_WIN32_KMT: case PL_HANDLE_HOST_PTR: case PL_HANDLE_FD: case PL_HANDLE_MTL_TEX: case PL_HANDLE_IOSURFACE: pl_unreachable(); } return true; error: PL_ERR(gpu, "Failed exporting GL texture!"); return false; } static const char *fb_err_str(GLenum err) { switch (err) { #define CASE(name) case name: return #name CASE(GL_FRAMEBUFFER_COMPLETE); CASE(GL_FRAMEBUFFER_UNDEFINED); CASE(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT); CASE(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT); CASE(GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS); CASE(GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER); CASE(GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER); CASE(GL_FRAMEBUFFER_UNSUPPORTED); CASE(GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE); CASE(GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS); #undef CASE default: return "unknown error"; } } pl_tex gl_tex_create(pl_gpu gpu, const struct pl_tex_params *params) { const gl_funcs *gl = gl_funcs_get(gpu); if (!MAKE_CURRENT()) return NULL; struct pl_gl *p = PL_PRIV(gpu); struct pl_tex_t *tex = pl_zalloc_obj(NULL, tex, struct pl_tex_gl); tex->params = *params; tex->params.initial_data = NULL; tex->sampler_type = PL_SAMPLER_NORMAL; struct pl_tex_gl *tex_gl = PL_PRIV(tex); const struct gl_format **fmtp = PL_PRIV(params->format); const struct gl_format *fmt = *fmtp; *tex_gl = (struct pl_tex_gl) { .format = fmt->fmt, .iformat = fmt->ifmt, .type = fmt->type, .barrier = tex_barrier(tex), .fd = -1, }; static const GLint targets[] = { [1] = GL_TEXTURE_1D, [2] = GL_TEXTURE_2D, [3] = GL_TEXTURE_3D, }; int dims = pl_tex_params_dimension(*params); pl_assert(dims >= 1 && dims <= 3); tex_gl->target = targets[dims]; gl->GenTextures(1, &tex_gl->texture); gl->BindTexture(tex_gl->target, tex_gl->texture); if (params->import_handle) { if (!gl_tex_import(gpu, params->import_handle, ¶ms->shared_mem, tex)) goto error; } else { gl->PixelStorei(GL_UNPACK_ALIGNMENT, 1); switch (dims) { case 1: gl->TexImage1D(tex_gl->target, 0, tex_gl->iformat, params->w, 0, tex_gl->format, tex_gl->type, params->initial_data); break; case 2: gl->TexImage2D(tex_gl->target, 0, tex_gl->iformat, params->w, params->h, 0, tex_gl->format, tex_gl->type, params->initial_data); break; case 3: gl->TexImage3D(tex_gl->target, 0, tex_gl->iformat, params->w, params->h, params->d, 0, tex_gl->format, tex_gl->type, params->initial_data); break; } gl->PixelStorei(GL_UNPACK_ALIGNMENT, 4); } if (params->export_handle) { if (!gl_tex_export(gpu, params->export_handle, params->initial_data, tex)) goto error; } gl->BindTexture(tex_gl->target, 0); if (!gl_check_err(gpu, "gl_tex_create: texture")) goto error; bool need_fbo = tex->params.renderable; if (tex->params.blit_src || tex->params.blit_dst) { if (dims != 2) { PL_ERR(gpu, "Blittable textures may only be 2D!"); goto error; } need_fbo = true; } bool can_fbo = tex->params.format->caps & PL_FMT_CAP_RENDERABLE && tex->params.d == 0; // Try creating an FBO for host-readable textures, since this allows // reading back with glReadPixels instead of glGetTexImage. (Additionally, // GLES does not support glGetTexImage) if (tex->params.host_readable && (can_fbo || p->gles_ver)) need_fbo = true; if (need_fbo) { if (!can_fbo) { PL_ERR(gpu, "Trying to create a renderable/blittable/readable " "texture with an incompatible (non-renderable) format!"); goto error; } gl->GenFramebuffers(1, &tex_gl->fbo); gl->BindFramebuffer(GL_DRAW_FRAMEBUFFER, tex_gl->fbo); switch (dims) { case 1: gl->FramebufferTexture1D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_1D, tex_gl->texture, 0); break; case 2: gl->FramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex_gl->texture, 0); break; case 3: pl_unreachable(); } GLenum err = gl->CheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); if (err != GL_FRAMEBUFFER_COMPLETE) { gl->BindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); PL_ERR(gpu, "Failed creating framebuffer: %s", fb_err_str(err)); goto error; } if (params->host_readable && p->gles_ver) { GLint read_type = 0, read_fmt = 0; gl->GetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &read_type); gl->GetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &read_fmt); if (read_type != tex_gl->type || read_fmt != tex_gl->format) { gl->BindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); PL_ERR(gpu, "Trying to create host_readable texture whose " "implementation-defined pixel read format " "(type=0x%X, fmt=0x%X) does not match the texture's " "internal format (type=0x%X, fmt=0x%X)! This is a " "GLES/driver limitation, there's little we can do " "about it.", read_type, read_fmt, tex_gl->type, tex_gl->format); goto error; } } gl->BindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); if (!gl_check_err(gpu, "gl_tex_create: fbo")) goto error; } RELEASE_CURRENT(); return tex; error: gl_tex_destroy(gpu, tex); RELEASE_CURRENT(); return NULL; } static bool gl_fb_query(pl_gpu gpu, int fbo, struct pl_fmt_t *fmt, struct gl_format *glfmt) { const gl_funcs *gl = gl_funcs_get(gpu); struct pl_gl *p = PL_PRIV(gpu); *fmt = (struct pl_fmt_t) { .name = "fbo", .type = PL_FMT_UNKNOWN, .caps = PL_FMT_CAP_RENDERABLE | PL_FMT_CAP_BLITTABLE | PL_FMT_CAP_BLENDABLE, .num_components = 4, .component_depth = {8, 8, 8, 8}, // default to rgba8 .sample_order = {0, 1, 2, 3}, }; *glfmt = (struct gl_format) { .fmt = GL_RGBA, }; bool can_query = gl_test_ext(gpu, "GL_ARB_framebuffer_object", 30, 20); if (!fbo && p->gles_ver && p->gles_ver < 30) can_query = false; // can't query default framebuffer on GLES 2.0 if (can_query) { gl->BindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); GLenum obj = p->gles_ver ? GL_BACK : GL_BACK_LEFT; if (fbo != 0) obj = GL_COLOR_ATTACHMENT0; GLint type = 0; gl->GetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, obj, GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE, &type); switch (type) { case GL_FLOAT: fmt->type = PL_FMT_FLOAT; break; case GL_INT: fmt->type = PL_FMT_SINT; break; case GL_UNSIGNED_INT: fmt->type = PL_FMT_UINT; break; case GL_SIGNED_NORMALIZED: fmt->type = PL_FMT_SNORM; break; case GL_UNSIGNED_NORMALIZED: fmt->type = PL_FMT_UNORM; break; default: fmt->type = PL_FMT_UNKNOWN; break; } gl->GetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, obj, GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE, &fmt->component_depth[0]); gl->GetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, obj, GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE, &fmt->component_depth[1]); gl->GetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, obj, GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE, &fmt->component_depth[2]); gl->GetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, obj, GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE, &fmt->component_depth[3]); gl->BindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); gl_check_err(gpu, "gl_fb_query"); if (!fmt->component_depth[0]) { PL_INFO(gpu, "OpenGL framebuffer did not export depth information," "assuming 8-bit framebuffer"); for (int i = 0; i < PL_ARRAY_SIZE(fmt->component_depth); i++) fmt->component_depth[i] = 8; } // Strip missing components from component map while (!fmt->component_depth[fmt->num_components - 1]) { fmt->num_components--; pl_assert(fmt->num_components); } } int gpu_bits = 0; for (int i = 0; i < 4; i++) gpu_bits += fmt->component_depth[i]; fmt->internal_size = (gpu_bits + 7) / 8; size_t host_size = 0; switch (fmt->type) { case PL_FMT_UNKNOWN: fmt->opaque = true; return true; case PL_FMT_FLOAT: glfmt->type = GL_FLOAT; host_size = sizeof(float); break; case PL_FMT_UNORM: case PL_FMT_UINT: if (gpu_bits > 32) { glfmt->type = GL_UNSIGNED_SHORT; host_size = sizeof(uint16_t); } else { glfmt->type = GL_UNSIGNED_BYTE; host_size = sizeof(uint8_t); } break; case PL_FMT_SNORM: case PL_FMT_SINT: if (gpu_bits > 32) { glfmt->type = GL_SHORT; host_size = sizeof(int16_t); } else { glfmt->type = GL_BYTE; host_size = sizeof(int8_t); } break; case PL_FMT_TYPE_COUNT: pl_unreachable(); } fmt->texel_size = fmt->num_components * host_size; for (int i = 0; i < fmt->num_components; i++) fmt->host_bits[i] = 8 * host_size; fmt->caps |= PL_FMT_CAP_HOST_READABLE; return true; } pl_tex pl_opengl_wrap(pl_gpu gpu, const struct pl_opengl_wrap_params *params) { const gl_funcs *gl = gl_funcs_get(gpu); if (!MAKE_CURRENT()) return NULL; struct pl_gl *p = PL_PRIV(gpu); struct pl_tex_t *tex = pl_alloc_obj(NULL, tex, struct pl_tex_gl); struct pl_tex_gl *tex_gl = PL_PRIV(tex); *tex = (struct pl_tex_t) { .params = { .w = params->width, .h = params->height, .d = params->depth, }, }; pl_fmt fmt = NULL; const struct gl_format *glfmt = NULL; if (params->texture) { // Wrapping texture: Require matching iformat pl_assert(params->iformat); for (int i = 0; i < gpu->num_formats; i++) { const struct gl_format **glfmtp = PL_PRIV(gpu->formats[i]); if ((*glfmtp)->ifmt == params->iformat) { fmt = gpu->formats[i]; glfmt = *glfmtp; break; } } if (!fmt) { PL_ERR(gpu, "Failed mapping iformat %d to any equivalent `pl_fmt`", params->iformat); goto error; } } else { // Wrapping framebuffer: Allocate/infer generic FBO format fmt = pl_alloc_obj((void *) gpu, fmt, const struct gl_format *); glfmt = pl_alloc_ptr((void *) fmt, glfmt); const struct gl_format **glfmtp = PL_PRIV(fmt); *glfmtp = glfmt; if (!gl_fb_query(gpu, params->framebuffer, (struct pl_fmt_t *) fmt, (struct gl_format *) glfmt)) { PL_ERR(gpu, "Failed querying framebuffer specifics!"); pl_free((void *) fmt); goto error; } } *tex_gl = (struct pl_tex_gl) { .target = params->target, .texture = params->texture, .fbo = params->framebuffer, .wrapped_tex = !!params->texture, .wrapped_fb = params->framebuffer || !params->texture, .iformat = glfmt->ifmt, .format = glfmt->fmt, .type = glfmt->type, .fd = -1, }; int dims = pl_tex_params_dimension(tex->params); if (!tex_gl->target) { switch (dims) { case 1: tex_gl->target = GL_TEXTURE_1D; break; case 2: tex_gl->target = GL_TEXTURE_2D; break; case 3: tex_gl->target = GL_TEXTURE_3D; break; } } // Map texture-specific sampling metadata if (params->texture) { switch (params->target) { case GL_TEXTURE_1D: if (params->width || params->depth) { PL_ERR(gpu, "Invalid texture dimensions for GL_TEXTURE_1D"); goto error; } // fall through case GL_TEXTURE_2D: if (params->depth) { PL_ERR(gpu, "Invalid texture dimensions for GL_TEXTURE_2D"); goto error; } // fall through case 0: case GL_TEXTURE_3D: tex->sampler_type = PL_SAMPLER_NORMAL; break; case GL_TEXTURE_RECTANGLE: tex->sampler_type = PL_SAMPLER_RECT; break; case GL_TEXTURE_EXTERNAL_OES: tex->sampler_type = PL_SAMPLER_EXTERNAL; break; default: PL_ERR(gpu, "Failed mapping texture target %u to any equivalent " "`pl_sampler_type`", params->target); goto error; } } // Create optional extra fbo if needed/possible bool can_fbo = tex_gl->texture && (fmt->caps & PL_FMT_CAP_RENDERABLE) && tex->sampler_type != PL_SAMPLER_EXTERNAL && dims < 3; if (can_fbo && !tex_gl->fbo) { gl->GenFramebuffers(1, &tex_gl->fbo); gl->BindFramebuffer(GL_DRAW_FRAMEBUFFER, tex_gl->fbo); switch (dims) { case 1: gl->FramebufferTexture1D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex_gl->target, tex_gl->texture, 0); break; case 2: gl->FramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex_gl->target, tex_gl->texture, 0); break; } GLenum err = gl->CheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); if (err != GL_FRAMEBUFFER_COMPLETE) { gl->BindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); PL_ERR(gpu, "Failed creating framebuffer: error code %d", err); goto error; } if (p->gles_ver) { GLint read_type = 0, read_fmt = 0; gl->GetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &read_type); gl->GetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &read_fmt); tex->params.host_readable = read_type == tex_gl->type && read_fmt == tex_gl->format; } else { tex->params.host_readable = true; } gl->BindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); if (!gl_check_err(gpu, "pl_opengl_wrap: fbo")) goto error; } // Complete the process of inferring the texture capabilities tex->params.format = fmt; if (tex_gl->texture) { tex->params.sampleable = fmt->caps & PL_FMT_CAP_SAMPLEABLE; tex->params.storable = fmt->caps & PL_FMT_CAP_STORABLE; tex->params.host_writable = !fmt->opaque; tex->params.host_readable |= fmt->caps & PL_FMT_CAP_HOST_READABLE; } if (tex_gl->fbo || tex_gl->wrapped_fb) { tex->params.renderable = fmt->caps & PL_FMT_CAP_RENDERABLE; tex->params.host_readable |= fmt->caps & PL_FMT_CAP_HOST_READABLE; if (dims == 2 && (fmt->caps & PL_FMT_CAP_BLITTABLE)) { tex->params.blit_src = true; tex->params.blit_dst = true; } } tex_gl->barrier = tex_barrier(tex); RELEASE_CURRENT(); return tex; error: gl_tex_destroy(gpu, tex); RELEASE_CURRENT(); return NULL; } unsigned int pl_opengl_unwrap(pl_gpu gpu, pl_tex tex, unsigned int *out_target, int *out_iformat, unsigned int *out_fbo) { struct pl_tex_gl *tex_gl = PL_PRIV(tex); if (!tex_gl->texture) { PL_ERR(gpu, "Trying to call `pl_opengl_unwrap` on a pseudo-texture " "(perhaps obtained by `pl_swapchain_start_frame`?)"); return 0; } if (out_target) *out_target = tex_gl->target; if (out_iformat) *out_iformat = tex_gl->iformat; if (out_fbo) *out_fbo = tex_gl->fbo; return tex_gl->texture; } void gl_tex_invalidate(pl_gpu gpu, pl_tex tex) { const gl_funcs *gl = gl_funcs_get(gpu); struct pl_gl *p = PL_PRIV(gpu); struct pl_tex_gl *tex_gl = PL_PRIV(tex); if (!MAKE_CURRENT()) return; if (tex_gl->texture && p->has_invalidate_tex) gl->InvalidateTexImage(tex_gl->texture, 0); if ((tex_gl->wrapped_fb || tex_gl->fbo) && p->has_invalidate_fb) { GLenum attachment = tex_gl->fbo ? GL_COLOR_ATTACHMENT0 : GL_COLOR; gl->BindFramebuffer(GL_DRAW_FRAMEBUFFER, tex_gl->fbo); gl->InvalidateFramebuffer(GL_DRAW_FRAMEBUFFER, 1, &attachment); gl->BindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); } gl_check_err(gpu, "gl_tex_invalidate"); RELEASE_CURRENT(); } void gl_tex_clear_ex(pl_gpu gpu, pl_tex tex, const union pl_clear_color color) { const gl_funcs *gl = gl_funcs_get(gpu); if (!MAKE_CURRENT()) return; struct pl_tex_gl *tex_gl = PL_PRIV(tex); pl_assert(tex_gl->fbo || tex_gl->wrapped_fb); switch (tex->params.format->type) { case PL_FMT_UNKNOWN: case PL_FMT_FLOAT: case PL_FMT_UNORM: case PL_FMT_SNORM: gl->ClearColor(color.f[0], color.f[1], color.f[2], color.f[3]); break; case PL_FMT_UINT: gl->ClearColorIuiEXT(color.u[0], color.u[1], color.u[2], color.u[3]); break; case PL_FMT_SINT: gl->ClearColorIiEXT(color.i[0], color.i[1], color.i[2], color.i[3]); break; case PL_FMT_TYPE_COUNT: pl_unreachable(); } gl->BindFramebuffer(GL_DRAW_FRAMEBUFFER, tex_gl->fbo); gl->Clear(GL_COLOR_BUFFER_BIT); gl->BindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); gl_check_err(gpu, "gl_tex_clear"); RELEASE_CURRENT(); } void gl_tex_blit(pl_gpu gpu, const struct pl_tex_blit_params *params) { const gl_funcs *gl = gl_funcs_get(gpu); if (!MAKE_CURRENT()) return; struct pl_tex_gl *src_gl = PL_PRIV(params->src); struct pl_tex_gl *dst_gl = PL_PRIV(params->dst); pl_assert(src_gl->fbo || src_gl->wrapped_fb); pl_assert(dst_gl->fbo || dst_gl->wrapped_fb); gl->BindFramebuffer(GL_READ_FRAMEBUFFER, src_gl->fbo); gl->BindFramebuffer(GL_DRAW_FRAMEBUFFER, dst_gl->fbo); static const GLint filters[PL_TEX_SAMPLE_MODE_COUNT] = { [PL_TEX_SAMPLE_NEAREST] = GL_NEAREST, [PL_TEX_SAMPLE_LINEAR] = GL_LINEAR, }; pl_rect3d src_rc = params->src_rc, dst_rc = params->dst_rc; gl->BlitFramebuffer(src_rc.x0, src_rc.y0, src_rc.x1, src_rc.y1, dst_rc.x0, dst_rc.y0, dst_rc.x1, dst_rc.y1, GL_COLOR_BUFFER_BIT, filters[params->sample_mode]); gl->BindFramebuffer(GL_READ_FRAMEBUFFER, 0); gl->BindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); gl_check_err(gpu, "gl_tex_blit"); RELEASE_CURRENT(); } static int get_alignment(size_t pitch) { if (pitch % 8 == 0) return 8; if (pitch % 4 == 0) return 4; if (pitch % 2 == 0) return 2; return 1; } bool gl_tex_upload(pl_gpu gpu, const struct pl_tex_transfer_params *params) { const gl_funcs *gl = gl_funcs_get(gpu); struct pl_gl *p = PL_PRIV(gpu); pl_tex tex = params->tex; pl_fmt fmt = tex->params.format; pl_buf buf = params->buf; struct pl_tex_gl *tex_gl = PL_PRIV(tex); struct pl_buf_gl *buf_gl = buf ? PL_PRIV(buf) : NULL; // If the user requests asynchronous uploads, it's more efficient to do // them via a PBO - this allows us to skip blocking the caller, especially // when the host pointer can be imported directly. if (params->callback && !buf) { size_t buf_size = pl_tex_transfer_size(params); const size_t min_size = 32*1024; // 32 KiB if (buf_size >= min_size && buf_size <= gpu->limits.max_buf_size) return pl_tex_upload_pbo(gpu, params); } if (!MAKE_CURRENT()) return false; uintptr_t src = (uintptr_t) params->ptr; if (buf) { gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, buf_gl->buffer); src = buf_gl->offset + params->buf_offset; } bool misaligned = params->row_pitch % fmt->texel_size; int stride_w = params->row_pitch / fmt->texel_size; int stride_h = params->depth_pitch / params->row_pitch; int dims = pl_tex_params_dimension(tex->params); if (dims > 1) gl->PixelStorei(GL_UNPACK_ALIGNMENT, get_alignment(params->row_pitch)); int rows = pl_rect_h(params->rc); if (misaligned) { rows = 1; } else if (stride_w != pl_rect_w(params->rc)) { gl->PixelStorei(GL_UNPACK_ROW_LENGTH, stride_w); } int imgs = pl_rect_d(params->rc); if (stride_h != pl_rect_h(params->rc) || rows < stride_h) gl->PixelStorei(GL_UNPACK_IMAGE_HEIGHT, stride_h); gl->BindTexture(tex_gl->target, tex_gl->texture); gl_timer_begin(gpu, params->timer); switch (dims) { case 1: gl->TexSubImage1D(tex_gl->target, 0, params->rc.x0, pl_rect_w(params->rc), tex_gl->format, tex_gl->type, (void *) src); break; case 2: for (int y = params->rc.y0; y < params->rc.y1; y += rows) { gl->TexSubImage2D(tex_gl->target, 0, params->rc.x0, y, pl_rect_w(params->rc), rows, tex_gl->format, tex_gl->type, (void *) src); src += params->row_pitch * rows; } break; case 3: for (int z = params->rc.z0; z < params->rc.z1; z += imgs) { uintptr_t row_src = src; for (int y = params->rc.y0; y < params->rc.y1; y += rows) { gl->TexSubImage3D(tex_gl->target, 0, params->rc.x0, y, z, pl_rect_w(params->rc), rows, imgs, tex_gl->format, tex_gl->type, (void *) row_src); row_src = (uintptr_t) row_src + params->row_pitch * rows; } src += params->depth_pitch * imgs; } break; } gl_timer_end(gpu, params->timer); gl->BindTexture(tex_gl->target, 0); gl->PixelStorei(GL_UNPACK_ALIGNMENT, 4); gl->PixelStorei(GL_UNPACK_ROW_LENGTH, 0); gl->PixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0); if (buf) { gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); if (buf->params.host_mapped) { // Make sure the PBO is not reused until GL is done with it. If a // previous operation is pending, "update" it by creating a new // fence that will cover the previous operation as well. gl->DeleteSync(buf_gl->fence); buf_gl->fence = gl->FenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); } } if (params->callback) { PL_ARRAY_APPEND(gpu, p->callbacks, (struct gl_cb) { .sync = gl->FenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0), .callback = params->callback, .priv = params->priv, }); } bool ok = gl_check_err(gpu, "gl_tex_upload"); RELEASE_CURRENT(); return ok; } bool gl_tex_download(pl_gpu gpu, const struct pl_tex_transfer_params *params) { const gl_funcs *gl = gl_funcs_get(gpu); struct pl_gl *p = PL_PRIV(gpu); pl_tex tex = params->tex; pl_fmt fmt = tex->params.format; pl_buf buf = params->buf; struct pl_tex_gl *tex_gl = PL_PRIV(tex); struct pl_buf_gl *buf_gl = buf ? PL_PRIV(buf) : NULL; bool ok = true; if (params->callback && !buf) { size_t buf_size = pl_tex_transfer_size(params); const size_t min_size = 32*1024; // 32 KiB if (buf_size >= min_size && buf_size <= gpu->limits.max_buf_size) return pl_tex_download_pbo(gpu, params); } if (!MAKE_CURRENT()) return false; uintptr_t dst = (uintptr_t) params->ptr; if (buf) { gl->BindBuffer(GL_PIXEL_PACK_BUFFER, buf_gl->buffer); dst = buf_gl->offset + params->buf_offset; } pl_rect3d full = { 0, 0, 0, tex->params.w, PL_DEF(tex->params.h, 1), PL_DEF(tex->params.d, 1), }; bool misaligned = params->row_pitch % fmt->texel_size; int stride_w = params->row_pitch / fmt->texel_size; int stride_h = params->depth_pitch / params->row_pitch; int dims = pl_tex_params_dimension(tex->params); bool is_copy = pl_rect3d_eq(params->rc, full) && stride_w == tex->params.w && stride_h == PL_DEF(tex->params.h, 1) && !misaligned; gl_timer_begin(gpu, params->timer); if (tex_gl->fbo || tex_gl->wrapped_fb) { // We can use a more efficient path when we have an FBO available if (dims > 1) gl->PixelStorei(GL_PACK_ALIGNMENT, get_alignment(params->row_pitch)); int rows = pl_rect_h(params->rc); if (misaligned) { rows = 1; } else if (stride_w != tex->params.w) { gl->PixelStorei(GL_PACK_ROW_LENGTH, stride_w); } // No 3D framebuffers pl_assert(pl_rect_d(params->rc) == 1); gl->BindFramebuffer(GL_READ_FRAMEBUFFER, tex_gl->fbo); for (int y = params->rc.y0; y < params->rc.y1; y += rows) { gl->ReadPixels(params->rc.x0, y, pl_rect_w(params->rc), rows, tex_gl->format, tex_gl->type, (void *) dst); dst += params->row_pitch * rows; } gl->BindFramebuffer(GL_READ_FRAMEBUFFER, 0); gl->PixelStorei(GL_PACK_ALIGNMENT, 4); gl->PixelStorei(GL_PACK_ROW_LENGTH, 0); } else if (is_copy) { // We're downloading the entire texture gl->BindTexture(tex_gl->target, tex_gl->texture); gl->GetTexImage(tex_gl->target, 0, tex_gl->format, tex_gl->type, (void *) dst); gl->BindTexture(tex_gl->target, 0); } else { PL_ERR(gpu, "Partial downloads of 3D textures not implemented!"); ok = false; } gl_timer_end(gpu, params->timer); if (buf) { gl->BindBuffer(GL_PIXEL_PACK_BUFFER, 0); if (ok && buf->params.host_mapped) { gl->DeleteSync(buf_gl->fence); buf_gl->fence = gl->FenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); } } if (params->callback) { PL_ARRAY_APPEND(gpu, p->callbacks, (struct gl_cb) { .sync = gl->FenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0), .callback = params->callback, .priv = params->priv, }); } ok &= gl_check_err(gpu, "gl_tex_download"); RELEASE_CURRENT(); return ok; }