android_external_minigbm/drv.c
Roman Stratiienko 4f7758ffa1 minigbm: Use dmabuf inode as unique buffer id instead of handle
Handle has some limits and can't be used as unique buffer ID on systems
where display controller can scanout from CMA but GPU can work with both
CMA and VRAM.

Such systems have DRM/KMS and DRM/GPU drivers separated.
GBM frontend is always expecting handle for DRM/KMS driver.
In such system any attempt of importing the buffer with more
than 1 contiguous chunk into DRM/KMS driver will fail.

Using dma-buf inode as unique buffer ID is a common practice for
a last several years starting from [this kernel patch][1].

[1]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ed63bb1d1f8469586006a9ca63c42344401aa2ab
Change-Id: Ic3a69010d5da2f866a2252fc7e9eb29d67f8e1ed
Signed-off-by: Roman Stratiienko <r.stratiienko@gmail.com>
2026-07-16 00:53:22 -04:00

865 lines
20 KiB
C

/*
* Copyright 2016 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <xf86drm.h>
#ifdef __ANDROID__
#include <cutils/log.h>
#include <libgen.h>
#define MINIGBM_DEBUG "vendor.minigbm.debug"
#else
#define MINIGBM_DEBUG "MINIGBM_DEBUG"
#endif
#include "drv_helpers.h"
#include "drv_priv.h"
#include "util.h"
#ifdef DRV_EXTERNAL
extern struct backend *init_external_backend();
void drv_preload(bool load) {};
static const struct backend *drv_get_backend(int fd)
{
return init_external_backend();
}
#else
#ifdef DRV_AMDGPU
extern const struct backend backend_amdgpu;
#endif
#ifdef DRV_I915
extern const struct backend backend_i915;
#endif
#ifdef DRV_XE
extern const struct backend backend_xe;
#endif
#ifdef DRV_MSM
extern const struct backend backend_msm;
#endif
#ifdef DRV_VC4
extern const struct backend backend_vc4;
#endif
// Dumb / generic drivers
extern const struct backend backend_evdi;
extern const struct backend backend_marvell;
extern const struct backend backend_mediatek;
extern const struct backend backend_meson;
extern const struct backend backend_nouveau;
extern const struct backend backend_komeda;
extern const struct backend backend_radeon;
extern const struct backend backend_rockchip;
extern const struct backend backend_sun4i_drm;
extern const struct backend backend_synaptics;
extern const struct backend backend_virtgpu;
extern const struct backend backend_udl;
extern const struct backend backend_vkms;
extern const struct backend backend_mock;
static const struct backend *drv_backend_list[] = {
#ifdef DRV_AMDGPU
&backend_amdgpu,
#endif
#ifdef DRV_I915
&backend_i915,
#endif
#ifdef DRV_MSM
&backend_msm,
#endif
#ifdef DRV_VC4
&backend_vc4,
#endif
#ifdef DRV_XE
&backend_xe,
#endif
&backend_evdi, &backend_komeda, &backend_marvell, &backend_mediatek,
&backend_meson, &backend_nouveau, &backend_radeon, &backend_rockchip,
&backend_sun4i_drm, &backend_synaptics, &backend_udl, &backend_virtgpu,
&backend_vkms, &backend_mock
};
void drv_preload(bool load)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(drv_backend_list); i++) {
const struct backend *b = drv_backend_list[i];
if (b->preload)
b->preload(load);
}
}
static const struct backend *drv_get_backend(int fd)
{
drmVersionPtr drm_version;
unsigned int i;
drm_version = drmGetVersion(fd);
if (!drm_version)
return NULL;
for (i = 0; i < ARRAY_SIZE(drv_backend_list); i++) {
const struct backend *b = drv_backend_list[i];
if (!strcmp(drm_version->name, b->name)) {
drmFreeVersion(drm_version);
return b;
}
}
drmFreeVersion(drm_version);
return NULL;
}
#endif
struct driver *drv_create(int fd)
{
struct driver *drv;
int ret;
drv = (struct driver *)calloc(1, sizeof(*drv));
if (!drv)
return NULL;
const char *minigbm_debug;
minigbm_debug = drv_get_os_option(MINIGBM_DEBUG);
drv->compression =
(minigbm_debug == NULL) || (strstr(minigbm_debug, "nocompression") == NULL);
drv->log_bos = (minigbm_debug && strstr(minigbm_debug, "log_bos") != NULL);
drv->fd = fd;
drv->backend = drv_get_backend(fd);
if (!drv->backend)
goto free_driver;
if (pthread_mutex_init(&drv->buffer_table_lock, NULL))
goto free_driver;
drv->buffer_table = drmHashCreate();
if (!drv->buffer_table)
goto free_buffer_table_lock;
if (pthread_mutex_init(&drv->mappings_lock, NULL))
goto free_buffer_table;
drv->mappings = drv_array_init(sizeof(struct mapping));
if (!drv->mappings)
goto free_mappings_lock;
drv->combos = drv_array_init(sizeof(struct combination));
if (!drv->combos)
goto free_mappings;
if (drv->backend->init) {
ret = drv->backend->init(drv);
if (ret) {
drv_array_destroy(drv->combos);
goto free_mappings;
}
}
return drv;
free_mappings:
drv_array_destroy(drv->mappings);
free_mappings_lock:
pthread_mutex_destroy(&drv->mappings_lock);
free_buffer_table:
drmHashDestroy(drv->buffer_table);
free_buffer_table_lock:
pthread_mutex_destroy(&drv->buffer_table_lock);
free_driver:
free(drv);
return NULL;
}
void drv_destroy(struct driver *drv)
{
if (drv->backend->close)
drv->backend->close(drv);
drv_array_destroy(drv->combos);
drv_array_destroy(drv->mappings);
pthread_mutex_destroy(&drv->mappings_lock);
drmHashDestroy(drv->buffer_table);
pthread_mutex_destroy(&drv->buffer_table_lock);
free(drv);
}
int drv_get_fd(struct driver *drv)
{
return drv->fd;
}
const char *drv_get_name(struct driver *drv)
{
return drv->backend->name;
}
struct combination *drv_get_combination(struct driver *drv, uint32_t format, uint64_t use_flags)
{
struct combination *curr, *best;
if (format == DRM_FORMAT_NONE || use_flags == BO_USE_NONE)
return 0;
best = NULL;
uint32_t i;
for (i = 0; i < drv_array_size(drv->combos); i++) {
curr = drv_array_at_idx(drv->combos, i);
if ((format == curr->format) && use_flags == (curr->use_flags & use_flags))
if (!best || best->metadata.priority < curr->metadata.priority)
best = curr;
}
return best;
}
struct bo *drv_bo_new(struct driver *drv, uint32_t width, uint32_t height, uint32_t format,
uint64_t use_flags, bool is_test_buffer)
{
struct bo *bo;
bo = (struct bo *)calloc(1, sizeof(*bo));
if (!bo)
return NULL;
bo->drv = drv;
bo->meta.width = width;
bo->meta.height = height;
bo->meta.format = format;
bo->meta.use_flags = use_flags;
bo->meta.num_planes = drv_num_planes_from_format(format);
bo->is_test_buffer = is_test_buffer;
if (!bo->meta.num_planes) {
free(bo);
errno = EINVAL;
return NULL;
}
return bo;
}
static void drv_bo_mapping_destroy(struct bo *bo)
{
struct driver *drv = bo->drv;
uint32_t idx = 0;
/*
* This function is called right before the buffer is destroyed. It will free any mappings
* associated with the buffer.
*/
pthread_mutex_lock(&drv->mappings_lock);
for (size_t plane = 0; plane < bo->meta.num_planes; plane++) {
while (idx < drv_array_size(drv->mappings)) {
struct mapping *mapping =
(struct mapping *)drv_array_at_idx(drv->mappings, idx);
if (mapping->vma->inode != bo->inode) {
idx++;
continue;
}
if (!--mapping->vma->refcount) {
int ret = drv->backend->bo_unmap(bo, mapping->vma);
if (ret) {
pthread_mutex_unlock(&drv->mappings_lock);
assert(ret);
drv_loge("munmap failed\n");
return;
}
free(mapping->vma);
}
/* This shrinks and shifts the array, so don't increment idx. */
drv_array_remove(drv->mappings, idx);
}
}
pthread_mutex_unlock(&drv->mappings_lock);
}
/*
* Acquire a reference on plane buffers of the bo.
*/
static void drv_bo_acquire(struct bo *bo)
{
struct driver *drv = bo->drv;
pthread_mutex_lock(&drv->buffer_table_lock);
for (size_t plane = 0; plane < bo->meta.num_planes; plane++) {
uintptr_t num = 0;
if (!bo->inode) {
int fd = drv_bo_get_plane_fd(bo, plane);
bo->inode = drv_get_inode(fd);
close(fd);
}
if (!drmHashLookup(drv->buffer_table, bo->inode, (void **)&num))
drmHashDelete(drv->buffer_table, bo->inode);
drmHashInsert(drv->buffer_table, bo->inode, (void *)(num + 1));
}
pthread_mutex_unlock(&drv->buffer_table_lock);
}
/*
* Release a reference on plane buffers of the bo. Return true when the bo has lost all its
* references. Otherwise, return false.
*/
static bool drv_bo_release(struct bo *bo)
{
struct driver *drv = bo->drv;
uintptr_t num;
if (drv->backend->bo_release)
drv->backend->bo_release(bo);
pthread_mutex_lock(&drv->buffer_table_lock);
for (size_t plane = 0; plane < bo->meta.num_planes; plane++) {
if (!drmHashLookup(drv->buffer_table, bo->inode, (void **)&num)) {
drmHashDelete(drv->buffer_table, bo->inode);
if (num > 1) {
drmHashInsert(drv->buffer_table, bo->inode, (void *)(num - 1));
}
}
}
/* The same buffer can back multiple planes with different offsets. */
for (size_t plane = 0; plane < bo->meta.num_planes; plane++) {
if (!drmHashLookup(drv->buffer_table, bo->inode, (void **)&num)) {
/* num is positive if found in the hashmap. */
pthread_mutex_unlock(&drv->buffer_table_lock);
return false;
}
}
pthread_mutex_unlock(&drv->buffer_table_lock);
return true;
}
struct bo *drv_bo_create(struct driver *drv, uint32_t width, uint32_t height, uint32_t format,
uint64_t use_flags)
{
int ret;
struct bo *bo;
bool is_test_alloc;
is_test_alloc = use_flags & BO_USE_TEST_ALLOC;
use_flags &= ~BO_USE_TEST_ALLOC;
bo = drv_bo_new(drv, width, height, format, use_flags, is_test_alloc);
if (!bo)
return NULL;
ret = -EINVAL;
if (drv->backend->bo_compute_metadata) {
ret = drv->backend->bo_compute_metadata(bo, width, height, format, use_flags, NULL,
0);
if (!is_test_alloc && ret == 0)
ret = drv->backend->bo_create_from_metadata(bo);
} else if (!is_test_alloc) {
ret = drv->backend->bo_create(bo, width, height, format, use_flags);
}
if (ret) {
errno = -ret;
free(bo);
return NULL;
}
drv_bo_acquire(bo);
if (drv->log_bos)
drv_bo_log_info(bo, "legacy created");
return bo;
}
struct bo *drv_bo_create_with_modifiers(struct driver *drv, uint32_t width, uint32_t height,
uint32_t format, const uint64_t *modifiers, uint32_t count)
{
int ret;
struct bo *bo;
if (!drv->backend->bo_create_with_modifiers && !drv->backend->bo_compute_metadata) {
errno = ENOENT;
return NULL;
}
bo = drv_bo_new(drv, width, height, format, BO_USE_NONE, false);
if (!bo)
return NULL;
ret = -EINVAL;
if (drv->backend->bo_compute_metadata) {
ret = drv->backend->bo_compute_metadata(bo, width, height, format, BO_USE_NONE,
modifiers, count);
if (ret == 0)
ret = drv->backend->bo_create_from_metadata(bo);
} else {
ret = drv->backend->bo_create_with_modifiers(bo, width, height, format, modifiers,
count);
}
if (ret) {
free(bo);
return NULL;
}
drv_bo_acquire(bo);
if (drv->log_bos)
drv_bo_log_info(bo, "created");
return bo;
}
void drv_bo_destroy(struct bo *bo)
{
if (!bo->is_test_buffer && drv_bo_release(bo)) {
drv_bo_mapping_destroy(bo);
bo->drv->backend->bo_destroy(bo);
}
free(bo);
}
struct bo *drv_bo_import(struct driver *drv, struct drv_import_fd_data *data)
{
int ret;
size_t plane;
struct bo *bo;
off_t seek_end;
bo = drv_bo_new(drv, data->width, data->height, data->format, data->use_flags, false);
if (!bo)
return NULL;
ret = drv->backend->bo_import(bo, data);
if (ret) {
free(bo);
return NULL;
}
bo->inode = drv_get_inode(data->fds[0]);
drv_bo_acquire(bo);
bo->meta.format_modifier = data->format_modifier;
for (plane = 0; plane < bo->meta.num_planes; plane++) {
bo->meta.strides[plane] = data->strides[plane];
bo->meta.offsets[plane] = data->offsets[plane];
seek_end = lseek(data->fds[plane], 0, SEEK_END);
if (seek_end == (off_t)(-1)) {
drv_loge("lseek() failed with %s\n", strerror(errno));
goto destroy_bo;
}
lseek(data->fds[plane], 0, SEEK_SET);
if (plane == bo->meta.num_planes - 1 || data->offsets[plane + 1] == 0)
bo->meta.sizes[plane] = seek_end - data->offsets[plane];
else
bo->meta.sizes[plane] = data->offsets[plane + 1] - data->offsets[plane];
if ((int64_t)bo->meta.offsets[plane] + bo->meta.sizes[plane] > seek_end) {
drv_loge("buffer size is too large.\n");
goto destroy_bo;
}
bo->meta.total_size += bo->meta.sizes[plane];
}
if (drv->log_bos)
drv_bo_log_info(bo, "imported");
return bo;
destroy_bo:
drv_bo_destroy(bo);
return NULL;
}
void *drv_bo_map(struct bo *bo, const struct rectangle *rect, uint32_t map_flags,
struct mapping **map_data, size_t plane)
{
struct driver *drv = bo->drv;
uint32_t i;
uint8_t *addr;
struct mapping mapping = { 0 };
assert(rect->width >= 0);
assert(rect->height >= 0);
assert(rect->x + rect->width <= drv_bo_get_width(bo));
assert(rect->y + rect->height <= drv_bo_get_height(bo));
assert(BO_MAP_READ_WRITE & map_flags);
/* No CPU access for protected buffers. */
assert(!(bo->meta.use_flags & BO_USE_PROTECTED));
if (bo->is_test_buffer)
return MAP_FAILED;
mapping.rect = *rect;
mapping.refcount = 1;
pthread_mutex_lock(&drv->mappings_lock);
for (i = 0; i < drv_array_size(drv->mappings); i++) {
struct mapping *prior = (struct mapping *)drv_array_at_idx(drv->mappings, i);
if (prior->vma->inode != bo->inode || prior->vma->map_flags != map_flags)
continue;
if (rect->x != prior->rect.x || rect->y != prior->rect.y ||
rect->width != prior->rect.width || rect->height != prior->rect.height)
continue;
prior->refcount++;
*map_data = prior;
goto exact_match;
}
for (i = 0; i < drv_array_size(drv->mappings); i++) {
struct mapping *prior = (struct mapping *)drv_array_at_idx(drv->mappings, i);
if (prior->vma->inode != bo->inode || prior->vma->map_flags != map_flags)
continue;
prior->vma->refcount++;
mapping.vma = prior->vma;
goto success;
}
mapping.vma = calloc(1, sizeof(*mapping.vma));
if (!mapping.vma) {
*map_data = NULL;
pthread_mutex_unlock(&drv->mappings_lock);
return MAP_FAILED;
}
memcpy(mapping.vma->map_strides, bo->meta.strides, sizeof(mapping.vma->map_strides));
addr = drv->backend->bo_map(bo, mapping.vma, map_flags);
if (addr == MAP_FAILED) {
*map_data = NULL;
free(mapping.vma);
pthread_mutex_unlock(&drv->mappings_lock);
return MAP_FAILED;
}
mapping.vma->refcount = 1;
mapping.vma->addr = addr;
mapping.vma->inode = bo->inode;
mapping.vma->map_flags = map_flags;
success:
*map_data = drv_array_append(drv->mappings, &mapping);
exact_match:
drv_bo_invalidate(bo, *map_data);
addr = (uint8_t *)((*map_data)->vma->addr);
addr += drv_bo_get_plane_offset(bo, plane);
pthread_mutex_unlock(&drv->mappings_lock);
return (void *)addr;
}
int drv_bo_unmap(struct bo *bo, struct mapping *mapping)
{
struct driver *drv = bo->drv;
uint32_t i;
int ret = 0;
pthread_mutex_lock(&drv->mappings_lock);
if (--mapping->refcount)
goto out;
if (!--mapping->vma->refcount) {
ret = drv->backend->bo_unmap(bo, mapping->vma);
free(mapping->vma);
}
for (i = 0; i < drv_array_size(drv->mappings); i++) {
if (mapping == (struct mapping *)drv_array_at_idx(drv->mappings, i)) {
drv_array_remove(drv->mappings, i);
break;
}
}
out:
pthread_mutex_unlock(&drv->mappings_lock);
return ret;
}
bool drv_bo_cached(struct bo *bo)
{
return bo->meta.cached;
}
int drv_bo_invalidate(struct bo *bo, struct mapping *mapping)
{
int ret = 0;
assert(mapping);
assert(mapping->vma);
assert(mapping->refcount > 0);
assert(mapping->vma->refcount > 0);
if (bo->drv->backend->bo_invalidate)
ret = bo->drv->backend->bo_invalidate(bo, mapping);
return ret;
}
int drv_bo_flush(struct bo *bo, struct mapping *mapping)
{
int ret = 0;
assert(mapping);
assert(mapping->vma);
assert(mapping->refcount > 0);
assert(mapping->vma->refcount > 0);
if (bo->drv->backend->bo_flush)
ret = bo->drv->backend->bo_flush(bo, mapping);
return ret;
}
int drv_bo_flush_or_unmap(struct bo *bo, struct mapping *mapping)
{
int ret = 0;
assert(mapping);
assert(mapping->vma);
assert(mapping->refcount > 0);
assert(mapping->vma->refcount > 0);
assert(!(bo->meta.use_flags & BO_USE_PROTECTED));
if (bo->drv->backend->bo_flush)
ret = bo->drv->backend->bo_flush(bo, mapping);
else
ret = drv_bo_unmap(bo, mapping);
return ret;
}
uint32_t drv_bo_get_width(struct bo *bo)
{
return bo->meta.width;
}
uint32_t drv_bo_get_height(struct bo *bo)
{
return bo->meta.height;
}
size_t drv_bo_get_num_planes(struct bo *bo)
{
return bo->meta.num_planes;
}
union bo_handle drv_bo_get_plane_handle(struct bo *bo, size_t plane)
{
return bo->handle;
}
#ifndef DRM_RDWR
#define DRM_RDWR O_RDWR
#endif
int drv_bo_get_plane_fd(struct bo *bo, size_t plane)
{
int ret, fd;
assert(plane < bo->meta.num_planes);
if (bo->is_test_buffer)
return -EINVAL;
if (bo->drv->backend->bo_get_plane_fd) {
fd = bo->drv->backend->bo_get_plane_fd(bo, plane);
return fd;
}
ret = drmPrimeHandleToFD(bo->drv->fd, bo->handle.u32, DRM_CLOEXEC | DRM_RDWR, &fd);
// Older DRM implementations blocked DRM_RDWR, but gave a read/write mapping anyways
if (ret)
ret = drmPrimeHandleToFD(bo->drv->fd, bo->handle.u32, DRM_CLOEXEC, &fd);
if (ret)
drv_loge("Failed to get plane fd: %s\n", strerror(errno));
return (ret) ? ret : fd;
}
uint32_t drv_bo_get_plane_offset(struct bo *bo, size_t plane)
{
assert(plane < bo->meta.num_planes);
return bo->meta.offsets[plane];
}
uint32_t drv_bo_get_plane_size(struct bo *bo, size_t plane)
{
assert(plane < bo->meta.num_planes);
return bo->meta.sizes[plane];
}
uint32_t drv_bo_get_plane_stride(struct bo *bo, size_t plane)
{
assert(plane < bo->meta.num_planes);
return bo->meta.strides[plane];
}
uint64_t drv_bo_get_format_modifier(struct bo *bo)
{
return bo->meta.format_modifier;
}
uint32_t drv_bo_get_format(struct bo *bo)
{
return bo->meta.format;
}
uint32_t drv_bo_get_tiling(struct bo *bo)
{
return bo->meta.tiling;
}
uint64_t drv_bo_get_use_flags(struct bo *bo)
{
return bo->meta.use_flags;
}
size_t drv_bo_get_total_size(struct bo *bo)
{
return bo->meta.total_size;
}
void drv_bo_log_info(const struct bo *bo, const char *prefix)
{
const struct bo_metadata *meta = &bo->meta;
drv_logd("%s %s bo %p: %dx%d '%c%c%c%c' tiling %d plane %zu mod 0x%" PRIx64
" use 0x%" PRIx64 " size %zu\n",
prefix, bo->drv->backend->name, bo, meta->width, meta->height, meta->format & 0xff,
(meta->format >> 8) & 0xff, (meta->format >> 16) & 0xff,
(meta->format >> 24) & 0xff, meta->tiling, meta->num_planes, meta->format_modifier,
meta->use_flags, meta->total_size);
for (uint32_t i = 0; i < meta->num_planes; i++) {
drv_logd(" bo %p plane %d: offset %d size %d stride %d\n", bo, i, meta->offsets[i],
meta->sizes[i], meta->strides[i]);
}
}
uint32_t drv_bo_get_pixel_stride(struct bo *bo)
{
struct driver *drv = bo->drv;
uint32_t bytes_per_pixel = 0;
uint32_t map_stride = 0;
bytes_per_pixel = drv_bytes_per_pixel_from_format(bo->meta.format, 0);
if ((bo->meta.use_flags & BO_USE_SW_MASK) && drv->backend->bo_get_map_stride)
map_stride = drv->backend->bo_get_map_stride(bo);
if (!map_stride)
map_stride = bo->meta.strides[0];
return DIV_ROUND_UP(map_stride, bytes_per_pixel);
}
/*
* Map internal fourcc codes back to standard fourcc codes.
*/
uint32_t drv_get_standard_fourcc(uint32_t fourcc_internal)
{
return (fourcc_internal == DRM_FORMAT_YVU420_ANDROID) ? DRM_FORMAT_YVU420 : fourcc_internal;
}
void drv_resolve_format_and_use_flags(struct driver *drv, uint32_t format, uint64_t use_flags,
uint32_t *out_format, uint64_t *out_use_flags)
{
assert(drv->backend->resolve_format_and_use_flags);
drv->backend->resolve_format_and_use_flags(drv, format, use_flags, out_format,
out_use_flags);
}
void drv_log_prefix(enum drv_log_level level, const char *prefix, const char *func, int line,
const char *format, ...)
{
char buf[50];
snprintf(buf, sizeof(buf), "[%s:%s(%d)]", prefix, func, line);
va_list args;
va_start(args, format);
#ifdef __ANDROID__
int prio = ANDROID_LOG_ERROR;
switch (level) {
case DRV_LOGV:
prio = ANDROID_LOG_VERBOSE;
break;
case DRV_LOGD:
prio = ANDROID_LOG_DEBUG;
break;
case DRV_LOGI:
prio = ANDROID_LOG_INFO;
break;
case DRV_LOGE:
default:
break;
};
__android_log_vprint(prio, buf, format, args);
#else
fprintf(stderr, "%s ", buf);
vfprintf(stderr, format, args);
#endif
va_end(args);
}
int drv_resource_info(struct bo *bo, uint32_t strides[DRV_MAX_PLANES],
uint32_t offsets[DRV_MAX_PLANES], uint64_t *format_modifier)
{
for (uint32_t plane = 0; plane < bo->meta.num_planes; plane++) {
strides[plane] = bo->meta.strides[plane];
offsets[plane] = bo->meta.offsets[plane];
}
*format_modifier = bo->meta.format_modifier;
if (bo->drv->backend->resource_info)
return bo->drv->backend->resource_info(bo, strides, offsets, format_modifier);
return 0;
}
uint32_t drv_get_max_texture_2d_size(struct driver *drv)
{
if (drv->backend->get_max_texture_2d_size)
return drv->backend->get_max_texture_2d_size(drv);
return UINT32_MAX;
}