This commit is contained in:
elwrcl committed 2026-06-06 13:25:15 +03:00
1 parent 04d4dc8b09
commit 818cab85dc
12511 files changed
+6156641

No files matched your search

+1
View File
@@ -0,0 +1 @@
panloader
+23
View File
@@ -0,0 +1,23 @@
# Copyright © 2023 Collabora
# SPDX-License-Identifier: MIT
libpankmod_lib_files = files(
'pan_kmod.c',
'panfrost_kmod.c',
'panthor_kmod.c',
)
libpankmod_lib = static_library(
'pankmod_lib',
[libpankmod_lib_files],
include_directories : [inc_include, inc_src, inc_panfrost],
c_args : [no_override_init_args],
gnu_symbol_visibility : 'hidden',
dependencies: [dep_libdrm, idep_mesautil, idep_pan_packers, libpanfrost_model_dep],
build_by_default : false,
)
libpankmod_dep = declare_dependency(
include_directories: [inc_include, inc_src],
dependencies: [dep_libdrm],
)
+301
View File
@@ -0,0 +1,301 @@
/*
* Copyright © 2023 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#include <string.h>
#include <xf86drm.h>
#include "util/cache_ops.h"
#include "util/u_memory.h"
#include "util/macros.h"
#include "pan_kmod.h"
#include "pan_kmod_backend.h"
extern const struct pan_kmod_ops panfrost_kmod_ops;
extern const struct pan_kmod_ops panthor_kmod_ops;
static const struct {
const char *name;
const struct pan_kmod_ops *ops;
} drivers[] = {
{
"panfrost",
&panfrost_kmod_ops,
},
{
"panthor",
&panthor_kmod_ops,
},
};
static void *
default_zalloc(const struct pan_kmod_allocator *allocator, size_t size,
UNUSED bool transient)
{
return os_calloc(1, size);
}
static void
default_free(const struct pan_kmod_allocator *allocator, void *data)
{
os_free(data);
}
static const struct pan_kmod_allocator default_allocator = {
.zalloc = default_zalloc,
.free = default_free,
};
struct pan_kmod_dev *
pan_kmod_dev_create(int fd, uint32_t flags,
const struct pan_kmod_allocator *allocator)
{
drmVersionPtr version = drmGetVersion(fd);
struct pan_kmod_dev *dev = NULL;
if (!version)
return NULL;
if (!allocator)
allocator = &default_allocator;
const char *drv_name = version->name;
const struct pan_kmod_driver drv_info = {
.version = {
.major = version->version_major,
.minor = version->version_minor,
},
};
for (unsigned i = 0; i < ARRAY_SIZE(drivers); i++) {
if (!strcmp(drivers[i].name, drv_name)) {
const struct pan_kmod_ops *ops = drivers[i].ops;
dev = ops->dev_create(fd, flags, &drv_info, allocator);
break;
}
}
drmFreeVersion(version);
return dev;
}
void
pan_kmod_dev_destroy(struct pan_kmod_dev *dev)
{
dev->ops->dev_destroy(dev);
}
struct pan_kmod_bo *
pan_kmod_bo_alloc(struct pan_kmod_dev *dev, struct pan_kmod_vm *exclusive_vm,
uint64_t size, uint32_t flags)
{
struct pan_kmod_bo *bo;
bo = dev->ops->bo_alloc(dev, exclusive_vm, size, flags);
if (!bo)
return NULL;
/* We intentionally don't take the lock when filling the sparse array,
* because we just created the BO, and haven't exported it yet, so
* there's no risk of imports racing with our BO insertion.
*/
struct pan_kmod_bo **slot =
util_sparse_array_get(&dev->handle_to_bo.array, bo->handle);
if (!slot) {
mesa_loge("failed to allocate slot in the handle_to_bo array");
bo->dev->ops->bo_free(bo);
return NULL;
}
assert(*slot == NULL);
*slot = bo;
return bo;
}
void
pan_kmod_bo_put(struct pan_kmod_bo *bo)
{
if (!bo)
return;
int32_t refcnt = p_atomic_dec_return(&bo->refcnt);
assert(refcnt >= 0);
if (refcnt)
return;
struct pan_kmod_dev *dev = bo->dev;
simple_mtx_lock(&dev->handle_to_bo.lock);
/* If some import took a ref on this BO while we were trying to acquire the
* lock, skip the destruction.
*/
if (!p_atomic_read(&bo->refcnt)) {
struct pan_kmod_bo **slot = (struct pan_kmod_bo **)util_sparse_array_get(
&dev->handle_to_bo.array, bo->handle);
assert(slot);
*slot = NULL;
bo->dev->ops->bo_free(bo);
}
simple_mtx_unlock(&dev->handle_to_bo.lock);
}
struct pan_kmod_bo *
pan_kmod_bo_import(struct pan_kmod_dev *dev, int fd)
{
struct pan_kmod_bo *bo = NULL;
struct pan_kmod_bo **slot;
simple_mtx_lock(&dev->handle_to_bo.lock);
uint32_t handle;
int ret = drmPrimeFDToHandle(dev->fd, fd, &handle);
if (ret)
goto err_unlock;
slot = util_sparse_array_get(&dev->handle_to_bo.array, handle);
if (!slot)
goto err_close_handle;
if (*slot) {
bo = *slot;
p_atomic_inc(&bo->refcnt);
} else {
size_t size = lseek(fd, 0, SEEK_END);
if (size == 0 || size == (size_t)-1) {
mesa_loge("invalid dmabuf size");
goto err_close_handle;
}
bo = dev->ops->bo_import(dev, handle, size);
if (!bo)
goto err_close_handle;
*slot = bo;
}
assert(p_atomic_read(&bo->refcnt) > 0);
simple_mtx_unlock(&dev->handle_to_bo.lock);
return bo;
err_close_handle:
drmCloseBufferHandle(dev->fd, handle);
err_unlock:
simple_mtx_unlock(&dev->handle_to_bo.lock);
return NULL;
}
void
pan_kmod_flush_bo_map_syncs_locked(struct pan_kmod_dev *dev)
{
ASSERTED int ret = dev->ops->flush_bo_map_syncs(dev);
assert(!ret);
util_dynarray_foreach(&dev->pending_bo_syncs.array,
struct pan_kmod_deferred_bo_sync, sync)
sync->bo->has_pending_deferred_syncs = false;
util_dynarray_clear(&dev->pending_bo_syncs.array);
}
void
pan_kmod_flush_bo_map_syncs(struct pan_kmod_dev *dev)
{
if (dev->props.is_io_coherent)
return;
/* Barrier to make sure all flush/invalidate requests are effective. */
if (p_atomic_xchg(&dev->pending_bo_syncs.user_cache_ops_pending, false))
util_post_flush_inval_fence();
/* This can be racy, but that's fine, because we expect a future call to
* pan_kmod_flush_bo_map_syncs() if new ops are being added while we check
* this value.
*/
if (!util_dynarray_num_elements(&dev->pending_bo_syncs.array,
struct pan_kmod_deferred_bo_sync))
return;
simple_mtx_lock(&dev->pending_bo_syncs.lock);
pan_kmod_flush_bo_map_syncs_locked(dev);
simple_mtx_unlock(&dev->pending_bo_syncs.lock);
}
/* Arbitrary limit for now. Pick something bigger or make it configurable if it
* becomes problematic.
*/
#define MAX_PENDING_SYNC_OPS 4096
void
pan_kmod_queue_bo_map_sync(struct pan_kmod_bo *bo, uint64_t bo_offset,
void *cpu_ptr, uint64_t range,
enum pan_kmod_bo_sync_type type)
{
struct pan_kmod_dev *dev = bo->dev;
/* Nothing to do if the buffer is IO coherent or if the BO is not mapped
* cacheable.
*/
if (!(bo->flags & PAN_KMOD_BO_FLAG_WB_MMAP) ||
(bo->flags & PAN_KMOD_BO_FLAG_IO_COHERENT))
return;
/* If we have userspace cache flushing ops, use them instead of trapping
* through to the kernel.
*/
if (pan_kmod_can_sync_bo_map_from_userland(dev)) {
/* Pre-flush needs to be executed before each flush/inval operation, but
* we can batch the post flush/inval fence. util_pre_flush_fence() being
* a NOP on aarch64, it's effectively free there, but we keep it here for
* clarity (not sure we care about Mali on x86 to be honest :D).
*/
util_pre_flush_fence();
if (type == PAN_KMOD_BO_SYNC_CPU_CACHE_FLUSH)
util_flush_range_no_fence(cpu_ptr, range);
else
util_flush_inval_range_no_fence(cpu_ptr, range);
/* The util_pre_flush_inval_fence() is inserted by
* pan_kmod_flush_bo_map_syncs() to avoid unnecessary serialization when
* flush/invalidate operations are batched.
*/
p_atomic_set(&dev->pending_bo_syncs.user_cache_ops_pending, true);
return;
}
simple_mtx_lock(&dev->pending_bo_syncs.lock);
/* If we reach the limit, flush the pending ops before queuing new ones. */
if (util_dynarray_num_elements(&dev->pending_bo_syncs.array,
struct pan_kmod_deferred_bo_sync) >=
MAX_PENDING_SYNC_OPS)
pan_kmod_flush_bo_map_syncs_locked(dev);
uint64_t start = bo_offset & ~((uint64_t)util_cache_granularity() - 1);
uint64_t end = ALIGN_POT(bo_offset + range, util_cache_granularity());
struct pan_kmod_deferred_bo_sync new_sync = {
.bo = bo,
.start = start,
.size = end - start,
.type = type,
};
bo->has_pending_deferred_syncs = true;
util_dynarray_append(&dev->pending_bo_syncs.array, new_sync);
simple_mtx_unlock(&dev->pending_bo_syncs.lock);
}
+788
View File
@@ -0,0 +1,788 @@
/*
* Copyright © 2023 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
/*
* This file exposes some core KMD functionalities in a driver-agnostic way.
* The drivers are still assumed to be regular DRM drivers, such that some
* operations can be handled generically.
*
* Any operation that's too specific to be abstracted can either have a backend
* specific helper exposed through pan_kmod_<backend>.h, or no helper at all
* (in the latter case, users are expected to call the ioctl directly).
*
* If some operations are not natively supported by a KMD, the kmod backend
* should fail or emulate the functionality (if deemed necessary).
*/
#pragma once
#include <fcntl.h>
#include <inttypes.h>
#include <unistd.h>
#include <xf86drm.h>
#include "drm-uapi/drm.h"
#include "util/cache_ops.h"
#include "util/log.h"
#include "util/macros.h"
#include "util/os_file.h"
#include "util/os_mman.h"
#include "util/ralloc.h"
#include "util/simple_mtx.h"
#include "util/sparse_array.h"
#include "util/u_atomic.h"
#include "util/u_dynarray.h"
#include "kmod/panthor_kmod.h"
#include "pan_trace.h"
#if defined(__cplusplus)
extern "C" {
#endif
struct pan_kmod_dev;
/* GPU VM creation flags. */
enum pan_kmod_vm_flags {
/* Set if you want the VM to automatically assign virtual addresses when
* pan_kmod_vm_map(). If this flag is set, all pan_kmod_vm_map() calls
* must have va=PAN_KMOD_VM_MAP_AUTO_VA.
*/
PAN_KMOD_VM_FLAG_AUTO_VA = BITFIELD_BIT(0),
/* Let the backend know whether it should track the VM activity or not.
* Needed if PAN_KMOD_VM_OP_MODE_DEFER_TO_NEXT_IDLE_POINT is used.
*/
PAN_KMOD_VM_FLAG_TRACK_ACTIVITY = BITFIELD_BIT(1),
};
#define PAN_PGSIZE_4K (uint64_t)0x1000
#define PAN_PGSIZE_2M (uint64_t)0x200000
/* Object representing a GPU VM. */
struct pan_kmod_vm {
/* Page sizes supported by this VM. */
uint64_t pgsize_bitmap;
/* Combination of pan_kmod_vm_flags flags. */
uint32_t flags;
/* The VM handle returned by the KMD. If the KMD supports only one VM per
* context, this should be zero.
*/
uint32_t handle;
/* Device this VM was created from. */
struct pan_kmod_dev *dev;
};
/* Buffer object flags. */
enum pan_kmod_bo_flags {
/* Allow GPU execution on this buffer. */
PAN_KMOD_BO_FLAG_EXECUTABLE = BITFIELD_BIT(0),
/* Allocate memory when a GPU fault occurs instead of allocating
* up-front.
*/
PAN_KMOD_BO_FLAG_ALLOC_ON_FAULT = BITFIELD_BIT(1),
/* If set, the buffer object will never be CPU-mapped in userspace. */
PAN_KMOD_BO_FLAG_NO_MMAP = BITFIELD_BIT(2),
/* Set when the buffer object has been exported. Users don't directly
* control this flag, it's set when pan_kmod_bo_export() is called.
*/
PAN_KMOD_BO_FLAG_EXPORTED = BITFIELD_BIT(3),
/* Set when the buffer object has been impported. Users don't directly
* control this flag, it's set when pan_kmod_bo_import() is called.
*/
PAN_KMOD_BO_FLAG_IMPORTED = BITFIELD_BIT(4),
/* If set, the buffer in mapped GPU-uncached when pan_kmod_vm_map()
* is called.
*/
PAN_KMOD_BO_FLAG_GPU_UNCACHED = BITFIELD_BIT(5),
/* CPU map the buffer object in userspace by forcing the "Write-Back
* Cacheable" cacheability attribute. The mapping otherwise uses the
* "Non-Cacheable" attribute if the ACE-Lite coherency protocol isn't
* supported by the GPU.
*/
PAN_KMOD_BO_FLAG_WB_MMAP = BITFIELD_BIT(6),
/* Set by default when the device is IO coherent. We might want to
* make it optional at some point and pass a NON_COHERENT flag to
* the KMD to force non-coherent mappings on IO coherent setup.
*/
PAN_KMOD_BO_FLAG_IO_COHERENT = BITFIELD_BIT(7),
};
/* Allowed group priority flags. */
enum pan_kmod_group_allow_priority_flags {
/* Allow low priority group. */
PAN_KMOD_GROUP_ALLOW_PRIORITY_LOW = BITFIELD_BIT(0),
/* Allow medium priority group. */
PAN_KMOD_GROUP_ALLOW_PRIORITY_MEDIUM = BITFIELD_BIT(1),
/* Allow high priority group. */
PAN_KMOD_GROUP_ALLOW_PRIORITY_HIGH = BITFIELD_BIT(2),
/* Allow realtime priority group. */
PAN_KMOD_GROUP_ALLOW_PRIORITY_REALTIME = BITFIELD_BIT(3),
};
/* Buffer object. */
struct pan_kmod_bo {
/* Atomic reference count. The only reason we need to refcnt BOs at this
* level is because of how DRM prime import works: the import logic
* returns the handle of an existing object if the object was previously
* imported or was created by the driver.
* In order to prevent call GEM_CLOSE on an object that's still supposed
* to be active, we need count the number of users left.
*/
int32_t refcnt;
/* Size of the buffer object. */
uint64_t size;
/* Handle attached to the buffer object. */
uint32_t handle;
/* Combination of pan_kmod_bo_flags flags. */
uint32_t flags;
/* True if some deferred syncs targetting this BO are pending. */
bool has_pending_deferred_syncs;
/* If non-NULL, the buffer object can only by mapped on this VM. Typical
* the case for all internal/non-shareable buffers. The backend can
* optimize things based on this information. Calling pan_kmod_bo_export()
* on such buffer objects is forbidden.
*/
struct pan_kmod_vm *exclusive_vm;
/* The device this buffer object was created from. */
struct pan_kmod_dev *dev;
/* User private data. Use pan_kmod_bo_{set,get}_user_priv() to access it. */
void *user_priv;
};
/* List of GPU properties needed by the UMD. */
struct pan_kmod_dev_props {
/* GPU ID. */
uint64_t gpu_id;
/* GPU variant. */
uint32_t gpu_variant;
/* Bitmask encoding the number of shader cores exposed by the GPU. */
uint64_t shader_present;
/* Tiler features bits. */
uint32_t tiler_features;
/* Memory related feature bits. */
uint32_t mem_features;
/* MMU feature bits. */
uint32_t mmu_features;
#define MMU_FEATURES_VA_BITS(mmu_features) (mmu_features & 0xff)
/* Texture feature bits. */
uint32_t texture_features[4];
/* Maximum number of threads per core. */
uint32_t max_threads_per_core;
/* Maximum number of compute tasks per core. */
uint8_t max_tasks_per_core;
/* Maximum number of threads per workgroup. */
uint32_t max_threads_per_wg;
/* Number of registers per core. Can be used to determine the maximum
* number of threads that can be allocated for a specific shader based on
* the number of registers assigned to this shader.
*/
uint32_t num_registers_per_core;
/* Maximum number of thread-local storage instance per core.
* If the GPU doesn't have a THREAD_TLS_ALLOC register or the register
* value is zero, the backend should assign the value of max_threads_per_core
* here.
*/
uint32_t max_tls_instance_per_core;
/* AFBC feature bits. */
uint32_t afbc_features;
/* Support cycle count and timestamp propagation as job requirement */
bool gpu_can_query_timestamp;
/* Cycle counter and timestamp device coherent propogation is enabled */
bool timestamp_device_coherent;
/* GPU Timestamp frequency */
uint64_t timestamp_frequency;
/* A mask of flags containing the allowed group priorities. */
enum pan_kmod_group_allow_priority_flags allowed_group_priorities_mask;
/* Mask of BO flags supported by the KMD. */
uint32_t supported_bo_flags;
/* GPU is IO coherent, meaning BOs can be created with WB_MMAP without
* requiring explicit CPU cache maintenance.
*/
bool is_io_coherent;
};
/* Memory allocator for kmod internal allocations. */
struct pan_kmod_allocator {
/* Allocate and set to zero. */
void *(*zalloc)(const struct pan_kmod_allocator *allocator, size_t size,
bool transient);
/* Free. */
void (*free)(const struct pan_kmod_allocator *allocator, void *data);
/* Private data allocator data. Can be NULL if unused. */
void *priv;
};
/* Synchronization type. */
enum pan_kmod_sync_type {
PAN_KMOD_SYNC_TYPE_WAIT = 0,
PAN_KMOD_SYNC_TYPE_SIGNAL,
};
/* Synchronization operation. */
struct pan_kmod_sync_op {
/* Type of operation. */
enum pan_kmod_sync_type type;
/* Syncobj handle. */
uint32_t handle;
/* Syncobj point. Zero for binary syncobjs. */
uint64_t point;
};
/* Special value passed to pan_kmod_vm_map() to signify the VM it should
* automatically allocate a VA. Only valid if the VM was created with
* PAN_KMOD_VM_FLAG_AUTO_VA.
*/
#define PAN_KMOD_VM_MAP_AUTO_VA ~0ull
/* Special value return when the vm_map() operation failed. */
#define PAN_KMOD_VM_MAP_FAILED ~0ull
/* VM operations can be executed in different modes. */
enum pan_kmod_vm_op_mode {
/* The map/unmap operation is executed immediately, which might cause
* GPU faults if the GPU was still accessing buffers when we unmap or
* remap.
*/
PAN_KMOD_VM_OP_MODE_IMMEDIATE,
/* The map/unmap operation is executed asynchronously, and the user
* provides explicit wait/signal sync operations.
*/
PAN_KMOD_VM_OP_MODE_ASYNC,
/* The map/unmap operation is executed when the next GPU/VM idle-point
* is reached. This guarantees fault-free unmap/remap operations when the
* kmod user doesn't want to deal with synchronizations explicitly.
*/
PAN_KMOD_VM_OP_MODE_DEFER_TO_NEXT_IDLE_POINT,
};
/* VM operation type. */
enum pan_kmod_vm_op_type {
/* Map a buffer object. */
PAN_KMOD_VM_OP_TYPE_MAP,
/* Unmap a VA range. */
PAN_KMOD_VM_OP_TYPE_UNMAP,
/* Do nothing. Used as a way to execute sync operations on a VM queue,
* without touching the VM.
*/
PAN_KMOD_VM_OP_TYPE_SYNC_ONLY,
};
/* VM operation data. */
struct pan_kmod_vm_op {
/* The type of operation being requested. */
enum pan_kmod_vm_op_type type;
/* VA range. */
struct {
/* Start of the VA range.
* Must be PAN_KMOD_VM_MAP_AUTO_VA if PAN_KMOD_VM_FLAG_AUTO_VA was set
* at VM creation time. In that case, the allocated VA is returned
* in this field.
*/
uint64_t start;
/* Size of the VA range */
uint64_t size;
} va;
union {
/* Arguments specific to map operations. */
struct {
/* Buffer object to map. */
struct pan_kmod_bo *bo;
/* Offset in the buffer object. */
off_t bo_offset;
} map;
};
/* Synchronization operations attached to the VM operation. */
struct {
/* Number of synchronization operations. Must be zero if mode is
* PAN_KMOD_VM_OP_MODE_IMMEDIATE or PAN_KMOD_VM_OP_MODE_WAIT_IDLE.
*/
uint32_t count;
/* Array of synchronization operation descriptors. NULL if count is zero. */
const struct pan_kmod_sync_op *array;
} syncs;
};
/* VM state. */
enum pan_kmod_vm_state {
PAN_KMOD_VM_USABLE,
PAN_KMOD_VM_FAULTY,
};
/* Device flags. */
enum pan_kmod_dev_flags {
/* Set when the fd passed to pan_kmod_create() is expected to be
* owned by the device, iff the device creation succeeded.
*/
PAN_KMOD_DEV_FLAG_OWNS_FD = (1 << 0),
/* Force BO syncs through the kernel. */
PAN_KMOD_DEV_FLAG_MMAP_SYNC_THROUGH_KERNEL = (1 << 1),
};
/* Encode a virtual address range. */
struct pan_kmod_va_range {
/* Start of the VA range. */
uint64_t start;
/* Size of the VA range. */
uint64_t size;
};
/* KMD information. */
struct pan_kmod_driver {
/* KMD version. */
struct {
uint32_t major;
uint32_t minor;
} version;
};
/* KMD backend vtable.
*
* All methods described there are mandatory, unless explicitly flagged as
* optional.
*/
struct pan_kmod_ops {
/* Create a pan_kmod_dev object.
* Return NULL if the creation fails for any reason.
*/
struct pan_kmod_dev *(*dev_create)(
int fd, uint32_t flags, const struct pan_kmod_driver *drv_info,
const struct pan_kmod_allocator *allocator);
/* Destroy a pan_kmod_dev object. */
void (*dev_destroy)(struct pan_kmod_dev *dev);
/* Query the maxium user VA range.
* Users are free to use a subset of this range if they need less VA space.
* This method is optional, when not specified, kmod assumes the whole VA
* space (extracted from MMU_FEATURES.VA_BITS) is usable.
*/
struct pan_kmod_va_range (*dev_query_user_va_range)(
const struct pan_kmod_dev *dev);
/* Allocate a buffer object.
* Return NULL if the creation fails for any reason.
*/
struct pan_kmod_bo *(*bo_alloc)(struct pan_kmod_dev *dev,
struct pan_kmod_vm *exclusive_vm,
uint64_t size, uint32_t flags);
/* Free buffer object. */
void (*bo_free)(struct pan_kmod_bo *bo);
/* Import a buffer object.
* Return NULL if the import fails for any reason.
*/
struct pan_kmod_bo *(*bo_import)(struct pan_kmod_dev *dev, uint32_t handle,
uint64_t size);
/* Post export operations.
* Return 0 on success, -1 otherwise.
* This method is optional.
*/
int (*bo_export)(struct pan_kmod_bo *bo, int dmabuf_fd);
/* Get the file offset to use to mmap() a buffer object. */
off_t (*bo_get_mmap_offset)(struct pan_kmod_bo *bo);
/* Flush the pending BO map syncs. */
int (*flush_bo_map_syncs)(struct pan_kmod_dev *dev);
/* Wait for a buffer object to be ready for read or read/write accesses. */
bool (*bo_wait)(struct pan_kmod_bo *bo, int64_t timeout_ns,
bool for_read_only_access);
/* Make a buffer object evictable. This method is optional. */
void (*bo_make_evictable)(struct pan_kmod_bo *bo);
/* Make the buffer object unevictable. This method is optional. */
bool (*bo_make_unevictable)(struct pan_kmod_bo *bo);
/* Create a VM object. */
struct pan_kmod_vm *(*vm_create)(struct pan_kmod_dev *dev, uint32_t flags,
uint64_t va_start, uint64_t va_range);
/* Destroy a VM object. */
void (*vm_destroy)(struct pan_kmod_vm *vm);
/* Execute VM operations.
* Return 0 if the submission suceeds, -1 otherwise.
* For PAN_KMOD_VM_OP_MODE_IMMEDIATE submissions, the return value also
* reflects the successfulness of the VM operation, for other modes,
* if any of the VM operation fails, the VM might be flagged as unusable
* and users should create a new VM to recover.
*/
int (*vm_bind)(struct pan_kmod_vm *vm, enum pan_kmod_vm_op_mode mode,
struct pan_kmod_vm_op *ops, uint32_t op_count);
/* Query the VM state.
* This method is optional. When missing the VM is assumed to always be
* usable.
*/
enum pan_kmod_vm_state (*vm_query_state)(struct pan_kmod_vm *vm);
/* Query the current GPU timestamp */
uint64_t (*query_timestamp)(const struct pan_kmod_dev *dev);
/* Label the BO */
void (*bo_set_label)(struct pan_kmod_dev *dev, struct pan_kmod_bo *bo, const char *label);
};
static inline bool
pan_kmod_driver_version_at_least(const struct pan_kmod_driver *driver,
uint32_t major, uint32_t minor)
{
if (driver->version.major < major)
return false;
if (driver->version.major > major)
return true;
return driver->version.minor >= minor;
}
enum pan_kmod_bo_sync_type {
PAN_KMOD_BO_SYNC_CPU_CACHE_FLUSH,
PAN_KMOD_BO_SYNC_CPU_CACHE_FLUSH_AND_INVALIDATE,
};
/* Used to queue BO sync operations. */
struct pan_kmod_deferred_bo_sync {
struct pan_kmod_bo *bo;
uint64_t start;
uint64_t size;
enum pan_kmod_bo_sync_type type;
};
/* Device object. */
struct pan_kmod_dev {
/* FD attached to the device. */
int fd;
/* Device flags. */
uint32_t flags;
/* KMD backing this device. */
struct pan_kmod_driver driver;
/* KMD-agnostic device properties. */
struct pan_kmod_dev_props props;
/* kmod backend ops assigned at device creation. */
const struct pan_kmod_ops *ops;
/* DRM prime import returns the handle of a pre-existing GEM if we are
* importing an object that was created by us or previously imported.
* We need to make sure we return the same pan_kmod_bo in that case,
* otherwise freeing one pan_kmod_bo will make all other BOs sharing
* the same handle invalid.
*/
struct {
struct util_sparse_array array;
simple_mtx_t lock;
} handle_to_bo;
/* Pending BO syncs. */
struct {
bool user_cache_ops_pending;
struct util_dynarray array;
simple_mtx_t lock;
} pending_bo_syncs;
/* Allocator attached to the device. */
const struct pan_kmod_allocator *allocator;
/* User private data. Use pan_kmod_dev_{set,get}_user_priv() to access it. */
void *user_priv;
};
#define pan_kmod_ioctl(fd, op, arg) \
({ \
PAN_TRACE_SCOPE(PAN_TRACE_LIB_KMOD, "pan_kmod_ioctl op=" #op); \
drmIoctl(fd, op, arg); \
})
struct pan_kmod_dev *
pan_kmod_dev_create(int fd, uint32_t flags,
const struct pan_kmod_allocator *allocator);
void pan_kmod_dev_destroy(struct pan_kmod_dev *dev);
static inline struct pan_kmod_va_range
pan_kmod_dev_query_user_va_range(const struct pan_kmod_dev *dev)
{
if (dev->ops->dev_query_user_va_range)
return dev->ops->dev_query_user_va_range(dev);
return (struct pan_kmod_va_range){
.start = 0,
.size = 1ull << MMU_FEATURES_VA_BITS(dev->props.mmu_features),
};
}
static inline void
pan_kmod_dev_set_user_priv(struct pan_kmod_dev *dev, void *data)
{
dev->user_priv = data;
}
static inline void *
pan_kmod_dev_get_user_priv(struct pan_kmod_dev *dev)
{
return dev->user_priv;
}
struct pan_kmod_bo *pan_kmod_bo_alloc(struct pan_kmod_dev *dev,
struct pan_kmod_vm *exclusive_vm,
uint64_t size, uint32_t flags);
static inline struct pan_kmod_bo *
pan_kmod_bo_get(struct pan_kmod_bo *bo)
{
if (!bo)
return NULL;
ASSERTED int32_t refcnt = p_atomic_inc_return(&bo->refcnt);
/* If refcnt was zero before our increment, we're in trouble. */
assert(refcnt > 1);
return bo;
}
void pan_kmod_bo_put(struct pan_kmod_bo *bo);
static inline void *
pan_kmod_bo_cmdxchg_user_priv(struct pan_kmod_bo *bo, void *old_data,
void *new_data)
{
return (void *)p_atomic_cmpxchg((uintptr_t *)&bo->user_priv,
(uintptr_t)old_data, (uintptr_t)new_data);
}
static inline void
pan_kmod_bo_set_user_priv(struct pan_kmod_bo *bo, void *data)
{
bo->user_priv = data;
}
static inline void *
pan_kmod_bo_get_user_priv(const struct pan_kmod_bo *bo)
{
return bo->user_priv;
}
struct pan_kmod_bo *pan_kmod_bo_import(struct pan_kmod_dev *dev, int fd);
static inline int
pan_kmod_bo_export(struct pan_kmod_bo *bo)
{
PAN_TRACE_FUNC(PAN_TRACE_LIB_KMOD);
int fd;
if (drmPrimeHandleToFD(bo->dev->fd, bo->handle, DRM_CLOEXEC | DRM_RDWR,
&fd)) {
mesa_loge("drmPrimeHandleToFD() failed (err=%d)", errno);
return -1;
}
if (bo->dev->ops->bo_export && bo->dev->ops->bo_export(bo, fd)) {
close(fd);
return -1;
}
bo->flags |= PAN_KMOD_BO_FLAG_EXPORTED;
return fd;
}
static inline bool
pan_kmod_bo_wait(struct pan_kmod_bo *bo, int64_t timeout_ns,
bool for_read_only_access)
{
PAN_TRACE_FUNC(PAN_TRACE_LIB_KMOD);
return bo->dev->ops->bo_wait(bo, timeout_ns, for_read_only_access);
}
static inline void
pan_kmod_bo_make_evictable(struct pan_kmod_bo *bo)
{
if (bo->dev->ops->bo_make_evictable)
bo->dev->ops->bo_make_evictable(bo);
}
static inline bool
pan_kmod_bo_make_unevictable(struct pan_kmod_bo *bo)
{
if (bo->dev->ops->bo_make_unevictable)
return bo->dev->ops->bo_make_unevictable(bo);
return true;
}
static inline void *
pan_kmod_bo_mmap(struct pan_kmod_bo *bo, int prot, int flags, void *host_addr)
{
PAN_TRACE_FUNC(PAN_TRACE_LIB_KMOD);
off_t mmap_offset;
/* Don't bother trying an mmap() if it's not allowed. */
if (bo->flags & PAN_KMOD_BO_FLAG_NO_MMAP)
return MAP_FAILED;
mmap_offset = bo->dev->ops->bo_get_mmap_offset(bo);
if (mmap_offset < 0)
return MAP_FAILED;
host_addr =
os_mmap(host_addr, bo->size, prot, flags, bo->dev->fd, mmap_offset);
if (host_addr == MAP_FAILED)
mesa_loge("mmap(..., size=%" PRIu64 ", prot=%d, flags=0x%x) failed: %s",
bo->size, prot, flags, strerror(errno));
return host_addr;
}
static inline bool
pan_kmod_can_sync_bo_map_from_userland(struct pan_kmod_dev *dev)
{
return util_has_cache_ops() &&
!(dev->flags & PAN_KMOD_DEV_FLAG_MMAP_SYNC_THROUGH_KERNEL);
}
void pan_kmod_queue_bo_map_sync(struct pan_kmod_bo *bo, uint64_t bo_offset,
void *cpu_ptr, uint64_t range,
enum pan_kmod_bo_sync_type type);
void pan_kmod_flush_bo_map_syncs(struct pan_kmod_dev *dev);
static inline void
pan_kmod_set_bo_label(struct pan_kmod_dev *dev, struct pan_kmod_bo *bo, const char *label)
{
PAN_TRACE_FUNC(PAN_TRACE_LIB_KMOD);
if (dev->ops->bo_set_label)
dev->ops->bo_set_label(dev, bo, label);
}
static inline uint64_t
pan_kmod_bo_size(struct pan_kmod_bo *bo)
{
return bo->size;
}
static inline uint32_t
pan_kmod_bo_handle(struct pan_kmod_bo *bo)
{
return bo->handle;
}
static inline struct pan_kmod_vm *
pan_kmod_vm_create(struct pan_kmod_dev *dev, uint32_t flags, uint64_t va_start,
uint64_t va_range)
{
PAN_TRACE_FUNC(PAN_TRACE_LIB_KMOD);
return dev->ops->vm_create(dev, flags, va_start, va_range);
}
static inline void
pan_kmod_vm_destroy(struct pan_kmod_vm *vm)
{
PAN_TRACE_FUNC(PAN_TRACE_LIB_KMOD);
vm->dev->ops->vm_destroy(vm);
}
static inline int
pan_kmod_vm_bind(struct pan_kmod_vm *vm, enum pan_kmod_vm_op_mode mode,
struct pan_kmod_vm_op *ops, uint32_t op_count)
{
PAN_TRACE_FUNC(PAN_TRACE_LIB_KMOD);
return vm->dev->ops->vm_bind(vm, mode, ops, op_count);
}
static inline enum pan_kmod_vm_state
pan_kmod_vm_query_state(struct pan_kmod_vm *vm)
{
if (vm->dev->ops->vm_query_state)
return vm->dev->ops->vm_query_state(vm);
return PAN_KMOD_VM_USABLE;
}
static inline uint32_t
pan_kmod_vm_handle(struct pan_kmod_vm *vm)
{
return vm->handle;
}
static inline uint64_t
pan_kmod_query_timestamp(const struct pan_kmod_dev *dev)
{
return dev->ops->query_timestamp(dev);
}
#if defined(__cplusplus)
} // extern "C"
#endif
+142
View File
@@ -0,0 +1,142 @@
/*
* Copyright © 2023 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#pragma once
#include "util/log.h"
#include "pan_kmod.h"
static inline void
pan_kmod_dev_init(struct pan_kmod_dev *dev, int fd, uint32_t flags,
const struct pan_kmod_driver *drv_info,
const struct pan_kmod_ops *ops,
const struct pan_kmod_allocator *allocator)
{
simple_mtx_init(&dev->handle_to_bo.lock, mtx_plain);
util_sparse_array_init(&dev->handle_to_bo.array,
sizeof(struct pan_kmod_bo *), 512);
simple_mtx_init(&dev->pending_bo_syncs.lock, mtx_plain);
util_dynarray_init(&dev->pending_bo_syncs.array, NULL);
dev->driver = *drv_info;
dev->fd = fd;
dev->flags = flags;
dev->ops = ops;
dev->allocator = allocator;
}
static inline void
pan_kmod_dev_cleanup(struct pan_kmod_dev *dev)
{
if (dev->flags & PAN_KMOD_DEV_FLAG_OWNS_FD)
close(dev->fd);
util_dynarray_fini(&dev->pending_bo_syncs.array);
util_sparse_array_finish(&dev->handle_to_bo.array);
simple_mtx_destroy(&dev->handle_to_bo.lock);
simple_mtx_destroy(&dev->pending_bo_syncs.lock);
}
static inline void *
pan_kmod_alloc(const struct pan_kmod_allocator *allocator, size_t size)
{
return allocator->zalloc(allocator, size, false);
}
static inline void *
pan_kmod_alloc_transient(const struct pan_kmod_allocator *allocator,
size_t size)
{
return allocator->zalloc(allocator, size, true);
}
static inline void
pan_kmod_free(const struct pan_kmod_allocator *allocator, void *data)
{
return allocator->free(allocator, data);
}
static inline void *
pan_kmod_dev_alloc(struct pan_kmod_dev *dev, size_t size)
{
return pan_kmod_alloc(dev->allocator, size);
}
static inline void *
pan_kmod_dev_alloc_transient(struct pan_kmod_dev *dev, size_t size)
{
return pan_kmod_alloc_transient(dev->allocator, size);
}
static inline void
pan_kmod_dev_free(const struct pan_kmod_dev *dev, void *data)
{
return pan_kmod_free(dev->allocator, data);
}
static inline void
pan_kmod_bo_init(struct pan_kmod_bo *bo, struct pan_kmod_dev *dev,
struct pan_kmod_vm *exclusive_vm, uint64_t size, uint32_t flags,
uint32_t handle)
{
/* Set by default when the device is IO coherent. We might want to
* make it optional at some point and pass a NON_COHERENT flag to
* the KMD to force non-coherent mappings on IO coherent setup.
*/
if (dev->props.is_io_coherent)
flags |= PAN_KMOD_BO_FLAG_IO_COHERENT;
bo->dev = dev;
bo->exclusive_vm = exclusive_vm;
bo->size = size;
bo->flags = flags;
bo->handle = handle;
p_atomic_set(&bo->refcnt, 1);
}
void pan_kmod_flush_bo_map_syncs_locked(struct pan_kmod_dev *dev);
static inline void
pan_kmod_bo_cleanup(struct pan_kmod_bo *bo)
{
if (bo->has_pending_deferred_syncs) {
struct pan_kmod_dev *dev = bo->dev;
simple_mtx_lock(&dev->pending_bo_syncs.lock);
pan_kmod_flush_bo_map_syncs_locked(dev);
simple_mtx_unlock(&dev->pending_bo_syncs.lock);
}
}
static inline void
pan_kmod_vm_init(struct pan_kmod_vm *vm, struct pan_kmod_dev *dev,
uint32_t handle, uint32_t flags, uint64_t pgsize_bitmap)
{
vm->dev = dev;
vm->handle = handle;
vm->flags = flags;
vm->pgsize_bitmap = pgsize_bitmap;
}
static inline int
pan_kmod_vm_op_check(struct pan_kmod_vm *vm, enum pan_kmod_vm_op_mode mode,
struct pan_kmod_vm_op *op)
{
/* We should only have sync operations on an async VM bind request. */
if (mode != PAN_KMOD_VM_OP_MODE_ASYNC && op->syncs.count) {
mesa_loge("only PAN_KMOD_VM_OP_MODE_ASYNC can be passed sync operations");
return -1;
}
/* Make sure the PAN_KMOD_VM_FLAG_AUTO_VA and VA passed to the op match. */
if (op->type == PAN_KMOD_VM_OP_TYPE_MAP &&
!!(vm->flags & PAN_KMOD_VM_FLAG_AUTO_VA) !=
(op->va.start == PAN_KMOD_VM_MAP_AUTO_VA)) {
mesa_loge("op->va.start and vm->flags don't match");
return -1;
}
return 0;
}
+627
View File
@@ -0,0 +1,627 @@
/*
* Copyright © 2023 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <xf86drm.h>
#include "util/hash_table.h"
#include "util/macros.h"
#include "util/simple_mtx.h"
#include "util/stack_array.h"
#include "drm-uapi/panfrost_drm.h"
#include "pan_kmod_backend.h"
#include "pan_props.h"
/* Maximum kmod BO label length, including NUL-terminator */
#define PANFROST_BO_LABEL_MAXLEN 4096
const struct pan_kmod_ops panfrost_kmod_ops;
struct panfrost_kmod_vm {
struct pan_kmod_vm base;
};
struct panfrost_kmod_dev {
struct pan_kmod_dev base;
struct panfrost_kmod_vm *vm;
};
struct panfrost_kmod_bo {
struct pan_kmod_bo base;
/* This is actually the VA assigned to the BO at creation/import time.
* We don't control it, it's automatically assigned by the kernel driver.
*/
uint64_t offset;
};
/* Abstraction over the raw drm_panfrost_get_param ioctl for fetching
* information about devices.
*/
static __u64
panfrost_query_raw(int fd, enum drm_panfrost_param param, bool required,
unsigned default_value)
{
struct drm_panfrost_get_param get_param = {};
ASSERTED int ret;
get_param.param = param;
ret = pan_kmod_ioctl(fd, DRM_IOCTL_PANFROST_GET_PARAM, &get_param);
if (ret) {
assert(!required);
return default_value;
}
return get_param.value;
}
static void
panfrost_dev_query_thread_props(struct panfrost_kmod_dev *panfrost_dev)
{
struct pan_kmod_dev_props *props = &panfrost_dev->base.props;
const struct pan_kmod_dev *dev = &panfrost_dev->base;
int fd = dev->fd;
props->max_threads_per_core =
panfrost_query_raw(fd, DRM_PANFROST_PARAM_MAX_THREADS, true, 0);
if (!props->max_threads_per_core) {
switch (pan_arch(props->gpu_id)) {
case 4:
case 5:
props->max_threads_per_core = 256;
break;
case 6:
/* Bifrost, first generation */
props->max_threads_per_core = 384;
break;
case 7:
/* Bifrost, second generation (G31 is 512 but it doesn't matter) */
props->max_threads_per_core = 768;
break;
case 9:
/* Valhall, first generation. */
props->max_threads_per_core = 512;
break;
default:
assert(!"Unsupported arch");
}
}
props->max_threads_per_wg = panfrost_query_raw(
fd, DRM_PANFROST_PARAM_THREAD_MAX_WORKGROUP_SZ, true, 0);
if (!props->max_threads_per_wg)
props->max_threads_per_wg = props->max_threads_per_core;
uint32_t thread_features =
panfrost_query_raw(fd, DRM_PANFROST_PARAM_THREAD_FEATURES, true, 0);
props->max_tasks_per_core = MAX2(thread_features >> 24, 1);
props->num_registers_per_core = thread_features & 0xffff;
if (!props->num_registers_per_core) {
switch (pan_arch(props->gpu_id)) {
case 4:
case 5:
/* Assume we can always schedule max_threads_per_core when using 4
* registers per-shader or less.
*/
props->num_registers_per_core = props->max_threads_per_core * 4;
break;
case 6:
/* Assume we can always schedule max_threads_per_core for shader
* using the full per-shader register file (64 regs).
*/
props->num_registers_per_core = props->max_threads_per_core * 64;
break;
case 7:
case 9:
/* Assume we can always schedule max_threads_per_core for shaders
* using half the per-shader register file (32 regs).
*/
props->num_registers_per_core = props->max_threads_per_core * 32;
break;
default:
assert(!"Unsupported arch");
}
}
props->max_tls_instance_per_core =
panfrost_query_raw(fd, DRM_PANFROST_PARAM_THREAD_TLS_ALLOC, true, 0);
if (!props->max_tls_instance_per_core)
props->max_tls_instance_per_core = props->max_threads_per_core;
}
static void
panfrost_dev_query_props(struct panfrost_kmod_dev *panfrost_dev)
{
struct pan_kmod_dev_props *props = &panfrost_dev->base.props;
const struct pan_kmod_dev *dev = &panfrost_dev->base;
int fd = dev->fd;
memset(props, 0, sizeof(*props));
props->gpu_id =
(panfrost_query_raw(fd, DRM_PANFROST_PARAM_GPU_PROD_ID, true, 0) << 16) |
panfrost_query_raw(fd, DRM_PANFROST_PARAM_GPU_REVISION, true, 0);
props->shader_present =
panfrost_query_raw(fd, DRM_PANFROST_PARAM_SHADER_PRESENT, true, 0);
props->tiler_features =
panfrost_query_raw(fd, DRM_PANFROST_PARAM_TILER_FEATURES, true, 0);
props->mem_features =
panfrost_query_raw(fd, DRM_PANFROST_PARAM_MEM_FEATURES, true, 0);
props->mmu_features =
panfrost_query_raw(fd, DRM_PANFROST_PARAM_MMU_FEATURES, true, 0);
for (unsigned i = 0; i < ARRAY_SIZE(props->texture_features); i++) {
props->texture_features[i] = panfrost_query_raw(
fd, DRM_PANFROST_PARAM_TEXTURE_FEATURES0 + i, true, 0);
}
props->afbc_features =
panfrost_query_raw(fd, DRM_PANFROST_PARAM_AFBC_FEATURES, true, 0);
panfrost_dev_query_thread_props(panfrost_dev);
if (pan_kmod_driver_version_at_least(&dev->driver, 1, 3)) {
props->gpu_can_query_timestamp = true;
props->timestamp_frequency = panfrost_query_raw(
fd, DRM_PANFROST_PARAM_SYSTEM_TIMESTAMP_FREQUENCY, true, 0);
}
/* Device coherent timestamps are always enabled on panfrost */
props->timestamp_device_coherent = true;
/* Support for priorities was added in panfrost 1.5, assumes default
* priority as medium if the param doesn't exist. */
uint64_t prios =
panfrost_query_raw(fd, DRM_PANFROST_PARAM_ALLOWED_JM_CTX_PRIORITIES,
false, BITFIELD_BIT(PANFROST_JM_CTX_PRIORITY_MEDIUM));
if (prios & BITFIELD_BIT(PANFROST_JM_CTX_PRIORITY_LOW))
props->allowed_group_priorities_mask |= PAN_KMOD_GROUP_ALLOW_PRIORITY_LOW;
if (prios & BITFIELD_BIT(PANFROST_JM_CTX_PRIORITY_MEDIUM))
props->allowed_group_priorities_mask |=
PAN_KMOD_GROUP_ALLOW_PRIORITY_MEDIUM;
if (prios & BITFIELD_BIT(PANFROST_JM_CTX_PRIORITY_HIGH))
props->allowed_group_priorities_mask |=
PAN_KMOD_GROUP_ALLOW_PRIORITY_HIGH;
props->supported_bo_flags = PAN_KMOD_BO_FLAG_EXECUTABLE |
PAN_KMOD_BO_FLAG_ALLOC_ON_FAULT |
PAN_KMOD_BO_FLAG_NO_MMAP;
if (pan_kmod_driver_version_at_least(&dev->driver, 1, 6)) {
uint32_t selected_coherency =
panfrost_query_raw(fd, DRM_PANFROST_PARAM_SELECTED_COHERENCY, true,
DRM_PANFROST_GPU_COHERENCY_NONE);
props->supported_bo_flags |= PAN_KMOD_BO_FLAG_WB_MMAP;
props->is_io_coherent =
selected_coherency != DRM_PANFROST_GPU_COHERENCY_NONE;
}
}
static struct pan_kmod_dev *
panfrost_kmod_dev_create(int fd, uint32_t flags,
const struct pan_kmod_driver *drv_info,
const struct pan_kmod_allocator *allocator)
{
if (!pan_kmod_driver_version_at_least(drv_info, 1, 1)) {
mesa_loge("kernel driver is too old (requires at least 1.1, found %d.%d)",
drv_info->version.major, drv_info->version.minor);
return NULL;
}
struct panfrost_kmod_dev *panfrost_dev =
pan_kmod_alloc(allocator, sizeof(*panfrost_dev));
if (!panfrost_dev) {
mesa_loge("failed to allocate a panfrost_kmod_dev object");
return NULL;
}
pan_kmod_dev_init(&panfrost_dev->base, fd, flags, drv_info,
&panfrost_kmod_ops, allocator);
panfrost_dev_query_props(panfrost_dev);
return &panfrost_dev->base;
}
static void
panfrost_kmod_dev_destroy(struct pan_kmod_dev *dev)
{
struct panfrost_kmod_dev *panfrost_dev =
container_of(dev, struct panfrost_kmod_dev, base);
pan_kmod_dev_cleanup(dev);
pan_kmod_free(dev->allocator, panfrost_dev);
}
static uint32_t
to_panfrost_bo_flags(struct pan_kmod_dev *dev, uint32_t flags)
{
uint32_t panfrost_flags = 0;
if (pan_kmod_driver_version_at_least(&dev->driver, 1, 1)) {
/* The alloc-on-fault feature is only used for the tiler HEAP object,
* hence the name of the flag on panfrost.
*/
if (flags & PAN_KMOD_BO_FLAG_ALLOC_ON_FAULT)
panfrost_flags |= PANFROST_BO_HEAP;
if (!(flags & PAN_KMOD_BO_FLAG_EXECUTABLE))
panfrost_flags |= PANFROST_BO_NOEXEC;
}
if (flags & PAN_KMOD_BO_FLAG_WB_MMAP) {
assert(!(flags & PAN_KMOD_BO_FLAG_NO_MMAP));
panfrost_flags |= PANFROST_BO_WB_MMAP;
}
return panfrost_flags;
}
static struct pan_kmod_bo *
panfrost_kmod_bo_alloc(struct pan_kmod_dev *dev,
struct pan_kmod_vm *exclusive_vm, uint64_t size,
uint32_t flags)
{
/* The ioctl uses u32 for size. */
if ((uint64_t)(uint32_t)size != size)
return NULL;
/* We can't map GPU uncached. */
if (flags & PAN_KMOD_BO_FLAG_GPU_UNCACHED)
return NULL;
struct panfrost_kmod_bo *bo = pan_kmod_dev_alloc(dev, sizeof(*bo));
if (!bo)
return NULL;
struct drm_panfrost_create_bo req = {
.size = size,
.flags = to_panfrost_bo_flags(dev, flags),
};
int ret = pan_kmod_ioctl(dev->fd, DRM_IOCTL_PANFROST_CREATE_BO, &req);
if (ret) {
mesa_loge("DRM_IOCTL_PANFROST_CREATE_BO failed (err=%d)", errno);
goto err_free_bo;
}
pan_kmod_bo_init(&bo->base, dev, exclusive_vm, req.size, flags, req.handle);
bo->offset = req.offset;
return &bo->base;
err_free_bo:
pan_kmod_dev_free(dev, bo);
return NULL;
}
static void
panfrost_kmod_bo_free(struct pan_kmod_bo *bo)
{
pan_kmod_bo_cleanup(bo);
drmCloseBufferHandle(bo->dev->fd, bo->handle);
pan_kmod_dev_free(bo->dev, bo);
}
static struct pan_kmod_bo *
panfrost_kmod_bo_import(struct pan_kmod_dev *dev, uint32_t handle,
uint64_t size)
{
struct panfrost_kmod_bo *panfrost_bo =
pan_kmod_dev_alloc(dev, sizeof(*panfrost_bo));
if (!panfrost_bo) {
mesa_loge("failed to allocate a panfrost_kmod_bo object");
return NULL;
}
struct drm_panfrost_get_bo_offset get_bo_offset = {.handle = handle, 0};
int ret =
pan_kmod_ioctl(dev->fd, DRM_IOCTL_PANFROST_GET_BO_OFFSET,
&get_bo_offset);
if (ret) {
mesa_loge("DRM_IOCTL_PANFROST_GET_BO_OFFSET failed (err=%d)", errno);
goto err_free_bo;
}
panfrost_bo->offset = get_bo_offset.offset;
uint32_t flags = PAN_KMOD_BO_FLAG_IMPORTED;
if (pan_kmod_driver_version_at_least(&dev->driver, 1, 6)) {
struct drm_panfrost_query_bo_info args = {
.handle = handle,
};
ret = drmIoctl(dev->fd, DRM_IOCTL_PANFROST_QUERY_BO_INFO, &args);
if (ret) {
mesa_loge("PANFROST_BO_QUERY_INFO failed (err=%d)", errno);
goto err_free_bo;
}
/* FIXME: If the BO comes from a different subsystem
* (args.extra_flags & DRM_PANTHOR_BO_IS_IMPORTED), we should normally
* add extra DMA_BUF_IOCTL_SYNC calls around CPU accesses to ensure the
* CPU mapping consistency, but this is something we never worried about
* (we've always assumed exporters were exposing uncached mappings with
* NOP {begin,end}_cpu_access() implementations), and it worked fine until
* now.
* The long term plan is to hook up DMA_BUF_IOCTL_SYNC, but this requires
* more work.
*/
}
pan_kmod_bo_init(&panfrost_bo->base, dev, NULL, size, flags, handle);
return &panfrost_bo->base;
err_free_bo:
pan_kmod_dev_free(dev, panfrost_bo);
return NULL;
}
static off_t
panfrost_kmod_bo_get_mmap_offset(struct pan_kmod_bo *bo)
{
struct drm_panfrost_mmap_bo mmap_bo = {.handle = bo->handle};
int ret = pan_kmod_ioctl(bo->dev->fd, DRM_IOCTL_PANFROST_MMAP_BO,
&mmap_bo);
if (ret) {
fprintf(stderr, "DRM_IOCTL_PANFROST_MMAP_BO failed: %m\n");
assert(0);
}
return mmap_bo.offset;
}
static bool
panfrost_kmod_bo_wait(struct pan_kmod_bo *bo, int64_t timeout_ns,
bool for_read_only_access)
{
struct drm_panfrost_wait_bo req = {
.handle = bo->handle,
.timeout_ns = timeout_ns,
};
/* The ioctl returns >= 0 value when the BO we are waiting for is ready
* -1 otherwise.
*/
if (pan_kmod_ioctl(bo->dev->fd, DRM_IOCTL_PANFROST_WAIT_BO, &req) != -1)
return true;
assert(errno == ETIMEDOUT || errno == EBUSY);
return false;
}
static int
panfrost_kmod_flush_bo_map_syncs(struct pan_kmod_dev *dev)
{
STACK_ARRAY(struct drm_panfrost_bo_sync_op, panfrost_ops,
util_dynarray_num_elements(&dev->pending_bo_syncs.array,
struct pan_kmod_deferred_bo_sync));
uint32_t panfrost_count = 0;
util_dynarray_foreach(&dev->pending_bo_syncs.array,
struct pan_kmod_deferred_bo_sync, sync) {
panfrost_ops[panfrost_count++] = (struct drm_panfrost_bo_sync_op){
.handle = sync->bo->handle,
.type = sync->type == PAN_KMOD_BO_SYNC_CPU_CACHE_FLUSH
? PANFROST_BO_SYNC_CPU_CACHE_FLUSH
: PANFROST_BO_SYNC_CPU_CACHE_FLUSH_AND_INVALIDATE,
.offset = sync->start,
.size = sync->size,
};
}
struct drm_panfrost_sync_bo req = {
.ops = (uintptr_t)panfrost_ops,
.op_count = panfrost_count,
};
int ret = pan_kmod_ioctl(dev->fd, DRM_IOCTL_PANFROST_SYNC_BO, &req);
if (ret)
mesa_loge("DRM_IOCTL_PANFROST_BO_SYNC failed (err=%d)", errno);
STACK_ARRAY_FINISH(panfrost_ops);
return ret;
}
static void
panfrost_kmod_bo_make_evictable(struct pan_kmod_bo *bo)
{
struct drm_panfrost_madvise req = {
.handle = bo->handle,
.madv = PANFROST_MADV_DONTNEED,
};
pan_kmod_ioctl(bo->dev->fd, DRM_IOCTL_PANFROST_MADVISE, &req);
}
static bool
panfrost_kmod_bo_make_unevictable(struct pan_kmod_bo *bo)
{
struct drm_panfrost_madvise req = {
.handle = bo->handle,
.madv = PANFROST_MADV_WILLNEED,
};
if (pan_kmod_ioctl(bo->dev->fd, DRM_IOCTL_PANFROST_MADVISE, &req) == 0 &&
req.retained == 0)
return false;
return true;
}
/* The VA range is restricted by the kernel driver. Lower 32MB are reserved, and
* the address space is limited to 32-bit.
*/
#define PANFROST_KMOD_VA_START 0x2000000ull
#define PANFROST_KMOD_VA_END (1ull << 32)
static struct pan_kmod_va_range
panfrost_kmod_dev_query_user_va_range(const struct pan_kmod_dev *dev)
{
return (struct pan_kmod_va_range){
.start = PANFROST_KMOD_VA_START,
.size = PANFROST_KMOD_VA_END - PANFROST_KMOD_VA_START,
};
}
static struct pan_kmod_vm *
panfrost_kmod_vm_create(struct pan_kmod_dev *dev, uint32_t flags,
uint64_t va_start, uint64_t va_range)
{
struct panfrost_kmod_dev *panfrost_dev =
container_of(dev, struct panfrost_kmod_dev, base);
/* Only one VM per device. */
if (panfrost_dev->vm) {
mesa_loge("panfrost_kmod only supports one VM per device");
return NULL;
}
/* Panfrost kernel driver doesn't support userspace VA management. */
if (!(flags & PAN_KMOD_VM_FLAG_AUTO_VA)) {
mesa_loge("panfrost_kmod only supports PAN_KMOD_VM_FLAG_AUTO_VA");
assert(0);
return NULL;
}
struct panfrost_kmod_vm *vm = pan_kmod_dev_alloc(dev, sizeof(*vm));
if (!vm) {
mesa_loge("failed to allocate a panfrost_kmod_vm object");
return NULL;
}
pan_kmod_vm_init(&vm->base, dev, 0, flags, PAN_PGSIZE_4K);
panfrost_dev->vm = vm;
return &vm->base;
}
static void
panfrost_kmod_vm_destroy(struct pan_kmod_vm *vm)
{
struct panfrost_kmod_dev *panfrost_dev =
container_of(vm->dev, struct panfrost_kmod_dev, base);
panfrost_dev->vm = NULL;
pan_kmod_dev_free(vm->dev, vm);
}
static int
panfrost_kmod_vm_bind(struct pan_kmod_vm *vm, enum pan_kmod_vm_op_mode mode,
struct pan_kmod_vm_op *ops, uint32_t op_count)
{
UNUSED struct panfrost_kmod_vm *panfrost_vm =
container_of(vm, struct panfrost_kmod_vm, base);
/* We only support IMMEDIATE and WAIT_IDLE mode. Actually we always do
* WAIT_IDLE in practice, but it shouldn't matter.
*/
if (mode != PAN_KMOD_VM_OP_MODE_IMMEDIATE &&
mode != PAN_KMOD_VM_OP_MODE_DEFER_TO_NEXT_IDLE_POINT) {
mesa_loge("panfrost_kmod doesn't support mode=%d", mode);
assert(0);
return -1;
}
for (uint32_t i = 0; i < op_count; i++) {
if (ops[i].type == PAN_KMOD_VM_OP_TYPE_MAP) {
struct panfrost_kmod_bo *panfrost_bo =
container_of(ops[i].map.bo, struct panfrost_kmod_bo, base);
/* Panfrost kernel driver doesn't support userspace VA management. */
if (ops[i].va.start != PAN_KMOD_VM_MAP_AUTO_VA) {
mesa_loge("panfrost_kmod can only do auto-VA allocation");
assert(0);
return -1;
}
/* Panfrost kernel driver only support full BO mapping. */
if (ops[i].map.bo_offset != 0 ||
ops[i].va.size != ops[i].map.bo->size) {
mesa_loge("panfrost_kmod doesn't support partial BO mapping");
assert(0);
return -1;
}
ops[i].va.start = panfrost_bo->offset;
} else if (ops[i].type == PAN_KMOD_VM_OP_TYPE_UNMAP) {
/* Do nothing, unmapping is done at BO destruction time. */
} else {
/* We reject PAN_KMOD_VM_OP_TYPE_SYNC_ONLY as this implies
* supporting PAN_KMOD_VM_OP_MODE_ASYNC, which we don't support.
*/
mesa_loge("panfrost_kmod doesn't support op=%d", ops[i].type);
assert(0);
return -1;
}
}
return 0;
}
static uint64_t
panfrost_kmod_query_timestamp(const struct pan_kmod_dev *dev)
{
return panfrost_query_raw(dev->fd, DRM_PANFROST_PARAM_SYSTEM_TIMESTAMP,
false, 0);
}
static void
panfrost_kmod_bo_label(struct pan_kmod_dev *dev, struct pan_kmod_bo *bo, const char *label)
{
char truncated_label[PANFROST_BO_LABEL_MAXLEN];
if (!pan_kmod_driver_version_at_least(&dev->driver, 1, 4))
return;
if (strnlen(label, PANFROST_BO_LABEL_MAXLEN) == PANFROST_BO_LABEL_MAXLEN) {
strncpy(truncated_label, label, PANFROST_BO_LABEL_MAXLEN - 1);
truncated_label[PANFROST_BO_LABEL_MAXLEN - 1] = '\0';
label = truncated_label;
}
struct drm_panfrost_set_label_bo set_label =
(struct drm_panfrost_set_label_bo) {
.handle = bo->handle,
.label = (uint64_t)(uintptr_t)label,
};
int ret =
pan_kmod_ioctl(dev->fd, DRM_IOCTL_PANFROST_SET_LABEL_BO,
&set_label);
if (ret)
mesa_loge("DRM_IOCTL_PANFROST_SET_LABEL_BO failed (err=%d)", errno);
}
const struct pan_kmod_ops panfrost_kmod_ops = {
.dev_create = panfrost_kmod_dev_create,
.dev_destroy = panfrost_kmod_dev_destroy,
.dev_query_user_va_range = panfrost_kmod_dev_query_user_va_range,
.bo_alloc = panfrost_kmod_bo_alloc,
.bo_free = panfrost_kmod_bo_free,
.bo_import = panfrost_kmod_bo_import,
.bo_get_mmap_offset = panfrost_kmod_bo_get_mmap_offset,
.bo_wait = panfrost_kmod_bo_wait,
.flush_bo_map_syncs = panfrost_kmod_flush_bo_map_syncs,
.bo_make_evictable = panfrost_kmod_bo_make_evictable,
.bo_make_unevictable = panfrost_kmod_bo_make_unevictable,
.vm_create = panfrost_kmod_vm_create,
.vm_destroy = panfrost_kmod_vm_destroy,
.vm_bind = panfrost_kmod_vm_bind,
.query_timestamp = panfrost_kmod_query_timestamp,
.bo_set_label = panfrost_kmod_bo_label,
};
File diff suppressed because it is too large. Load diff
+37
View File
@@ -0,0 +1,37 @@
/*
* Copyright © 2023 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <stdint.h>
#if defined(__cplusplus)
extern "C" {
#endif
struct drm_panthor_csif_info;
struct pan_kmod_bo;
struct pan_kmod_dev;
struct pan_kmod_vm;
int panthor_kmod_bo_attach_sync_point(struct pan_kmod_bo *bo,
uint32_t sync_handle,
uint64_t sync_point, bool written);
int panthor_kmod_bo_get_sync_point(struct pan_kmod_bo *bo,
uint32_t *sync_handle, uint64_t *sync_point,
bool read_only);
uint32_t panthor_kmod_vm_sync_handle(struct pan_kmod_vm *vm);
uint64_t panthor_kmod_vm_sync_lock(struct pan_kmod_vm *vm);
void panthor_kmod_vm_sync_unlock(struct pan_kmod_vm *vm,
uint64_t new_sync_point);
uint32_t panthor_kmod_get_flush_id(const struct pan_kmod_dev *dev);
const struct drm_panthor_csif_info *
panthor_kmod_get_csif_props(const struct pan_kmod_dev *dev);
#if defined(__cplusplus)
} // extern "C"
#endif
+133
View File
@@ -0,0 +1,133 @@
# Copyright © 2018 Rob Clark
# Copyright © 2019 Collabora
# SPDX-License-Identifier: MIT
subdir('kmod')
pixel_format_versions = ['5', '6', '7', '9', '10', '12', '13', '14']
libpanfrost_pixel_format = []
deps_for_libpanfrost = [dep_libdrm, idep_pan_packers, idep_mesautil, libpanfrost_model_dep]
foreach ver : pixel_format_versions
libpanfrost_pixel_format += static_library(
'pan-format-v' + ver,
['pan_format.c', pan_packers],
include_directories : [inc_src, inc_include, inc_panfrost],
c_args : ['-DPAN_ARCH=' + ver],
gnu_symbol_visibility : 'hidden',
dependencies : [deps_for_libpanfrost, dep_valgrind],
)
endforeach
libpanfrost_per_arch = []
foreach ver : ['4', '5', '6', '7', '9', '10', '12', '13', '14']
libpanfrost_per_arch += static_library(
'pan-arch-v' + ver,
[
'pan_blend.c',
'pan_buffer.c',
'pan_desc.c',
'pan_fb.c',
'pan_fb_nir.c',
'pan_mod.c',
'pan_texture.c',
],
include_directories : [inc_include, inc_src, inc_panfrost],
c_args : ['-DPAN_ARCH=' + ver],
gnu_symbol_visibility : 'hidden',
dependencies : [deps_for_libpanfrost, idep_nir],
)
endforeach
libpanfrost_lib_files = files(
'pan_encoder.h',
'pan_blend.c',
'pan_clear.c',
'pan_earlyzs.c',
'pan_samples.c',
'pan_tiler.c',
'pan_layout.c',
'pan_scratch.c',
'pan_trace.c',
'pan_props.c',
'pan_util.c',
'pan_afbc.c',
)
libpanfrost_lib = static_library(
'panfrost_lib',
[libpanfrost_lib_files, pan_packers],
include_directories : [inc_include, inc_src, inc_panfrost],
c_args : [no_override_init_args, soong_compat_c_args],
gnu_symbol_visibility : 'hidden',
dependencies: [dep_libdrm, idep_nir, idep_mesautil, libpanfrost_model_dep],
build_by_default : false,
link_with: [libpanfrost_pixel_format, libpanfrost_per_arch, libpankmod_lib],
)
libpanfrost_dep = declare_dependency(
link_with: [libpanfrost_lib, libpanfrost_decode, libpanfrost_compiler, libpanfrost_pixel_format, libpanfrost_per_arch],
include_directories: [inc_include, inc_src, inc_panfrost],
dependencies: [deps_for_libpanfrost, libpankmod_dep, idep_nir],
)
if with_tests
test(
'panfrost_attribute',
executable(
'panfrost_attribute_test',
files('tests/test-attribute.c'),
c_args : [c_msvc_compat_args, no_override_init_args],
gnu_symbol_visibility : 'hidden',
include_directories : [inc_include, inc_src],
dependencies: [libpanfrost_dep],
),
suite : ['panfrost'],
)
test(
'panfrost_blend',
executable(
'panfrost_blend_test',
files('tests/test-blend.c'),
c_args : [c_msvc_compat_args, no_override_init_args],
gnu_symbol_visibility : 'hidden',
include_directories : [inc_include, inc_src],
dependencies: [libpanfrost_dep],
),
suite : ['panfrost'],
)
test(
'panfrost_clear',
executable(
'panfrost_clear_test',
files('tests/test-clear.c'),
c_args : [c_msvc_compat_args, no_override_init_args],
gnu_symbol_visibility : 'hidden',
include_directories : [inc_include, inc_src],
dependencies: [libpanfrost_dep],
),
suite : ['panfrost'],
)
test(
'panfrost_tests',
executable(
'panfrost_tests',
files(
'tests/test-earlyzs.cpp',
'tests/test-layout.cpp',
),
cpp_args : cpp_msvc_compat_args,
gnu_symbol_visibility : 'hidden',
include_directories : [inc_include, inc_src, inc_panfrost],
dependencies: [idep_gtest, libpanfrost_dep],
),
suite : ['panfrost'],
protocol : 'gtest',
)
endif
+213
View File
@@ -0,0 +1,213 @@
/*
* Copyright © 2023-2025 Amazon.com, Inc. or its affiliates.
* SPDX-License-Identifier: MIT
*/
#include "pan_afbc.h"
#include "pan_trace.h"
#include "util/detect_arch.h"
#include "util/u_cpu_detect.h"
#if DETECT_ARCH_AARCH64 || (DETECT_ARCH_ARM && !defined(__SOFTFP__))
/* armhf builds default to VFP, not NEON, and refuses to compile NEON
* intrinsics unless you tell it "no really". */
#if DETECT_ARCH_ARM
#pragma GCC target ("fpu=neon")
#endif
#include <arm_neon.h>
#endif
#if DETECT_ARCH_AARCH64
/* Arm A64 NEON intrinsics version. */
uint32_t
pan_afbc_payload_layout_packed(unsigned arch,
const struct pan_afbc_headerblock *headers,
struct pan_afbc_payload_extent *layout,
uint32_t nr_blocks, enum pipe_format format,
uint64_t modifier)
{
PAN_TRACE_FUNC(PAN_TRACE_LIB_AFBC);
uint32_t uncompressed_size =
pan_afbc_payload_uncompressed_size(format, modifier);
uint32_t body_size = 0;
alignas(16) static const uint8_t idx0[16] =
{ 4, 5, 6, ~0, 7, 8, 9, ~0, 10, 11, 12, ~0, 13, 14, 15, ~0 };
alignas(16) static const uint8_t idx1[16] =
{ 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60 };
alignas(16) static const uint8_t mask[16] =
{ 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 };
alignas(16) static const uint8_t ones[16] =
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
uint8x16_t vidx0 = vld1q_u8(idx0);
uint8x16_t vidx1 = vld1q_u8(idx1);
uint8x16_t vmask = vld1q_u8(mask);
uint8x16_t vones = vld1q_u8(ones);
for (uint32_t i = 0; i < nr_blocks; ++i) {
uint32_t payload_size = 0;
/* Skip sum if the 1st subblock is 0 (solid color encoding). */
if (arch < 7 || headers[i].payload.subblock_sizes[0] & 0x3f) {
uint8x16_t vhdr = vld1q_u8((uint8_t *)&headers[i]);
/* Dispatch 6-bit packed 16 subblock sizes into 8-bit vector. */
uint8x16_t v0 = vqtbl1q_u8(vhdr, vidx0);
uint8x16_t v1 = vreinterpretq_u8_u32(
vshrq_n_u32(vreinterpretq_u32_u8(v0), 6));
uint8x16_t v2 = vreinterpretq_u8_u32(
vshrq_n_u32(vreinterpretq_u32_u8(v0), 12));
uint8x16_t v3 = vreinterpretq_u8_u32(
vshrq_n_u32(vreinterpretq_u32_u8(v0), 18));
uint8x16x4_t vtbl = {{ v0, v1, v2, v3 }};
v0 = vqtbl4q_u8(vtbl, vidx1);
v0 = vandq_u8(v0, vmask);
/* Sum across vector. */
payload_size = vaddlvq_u8(v0);
/* Number of subblocks of size 1. */
v0 = vceqq_u8(v0, vones);
v0 = vandq_u8(v0, vones);
uint32_t nr_ones = vaddvq_u8(v0);
/* Payload size already stores subblocks of size 1. Fix-up the sum
* using the number of such subblocks. */
payload_size += nr_ones * (uncompressed_size - 1);
payload_size = ALIGN_POT(payload_size, 16);
}
layout[i].size = payload_size;
layout[i].offset = body_size;
body_size += payload_size;
}
return body_size;
}
#else
/* Arm A32 NEON intrinsics and generic version. */
uint32_t
pan_afbc_payload_layout_packed(unsigned arch,
const struct pan_afbc_headerblock *headers,
struct pan_afbc_payload_extent *layout,
uint32_t nr_blocks, enum pipe_format format,
uint64_t modifier)
{
PAN_TRACE_FUNC(PAN_TRACE_LIB_AFBC);
uint32_t uncompressed_size =
pan_afbc_payload_uncompressed_size(format, modifier);
uint32_t body_size = 0;
#if DETECT_ARCH_ARM && !defined(__SOFTFP__)
if (unlikely(!util_get_cpu_caps()->has_neon))
goto no_neon;
/* Arm A32 NEON intrinsics version. */
alignas(16) static const uint8_t idx0[2][8] =
{ { 4, 5, 6, ~0, 7, 8, 9, ~0 }, { 2, 3, 4, ~0, 5, 6, 7, ~0 } };
alignas(8) static const uint8_t idx1[8] =
{ 0, 4, 8, 12, 16, 20, 24, 28 };
alignas(16) static const uint8_t mask[16] =
{ 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 };
alignas(16) static const uint8_t ones[16] =
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
uint8x8_t vidx00 = vld1_u8(idx0[0]);
uint8x8_t vidx01 = vld1_u8(idx0[1]);
uint8x8_t vidx1 = vld1_u8(idx1);
uint8x16_t vmask = vld1q_u8(mask);
uint8x16_t vones = vld1q_u8(ones);
for (uint32_t i = 0; i < nr_blocks; ++i) {
uint32_t payload_size = 0;
/* Skip sum if the 1st subblock is 0 (solid color encoding). */
if (arch < 7 || headers[i].payload.subblock_sizes[0] & 0x3f) {
/* vld1_u8_x2() isn't widely available yet. */
uint8x8_t vhdr0 = vld1_u8(&headers[i].u8[0]);
uint8x8_t vhdr1 = vld1_u8(&headers[i].u8[8]);
uint8x8x2_t vhdr = {{ vhdr0, vhdr1 }};
/* Dispatch 6-bit packed 16 payload sizes into 8-bit vector. Note
* that the NEON TBL instr in A32 only supports doubleword operands
* while VSHR also supports quadword. Not sure how to mix doubleword
* and quadword intrinsics and get compilers to correctly alias D and
* Q registers though (128-bit register Q0 is an alias for the two
* consecutive 64-bit registers D0 and D1), so stick with doubleword
* intrinsics here. */
uint8x8_t v00 = vtbl2_u8(vhdr, vidx00);
uint8x8_t v01 = vtbl1_u8(vhdr1, vidx01);
uint8x8_t v10 = vreinterpret_u8_u32(
vshr_n_u32(vreinterpret_u32_u8(v00), 6));
uint8x8_t v11 = vreinterpret_u8_u32(
vshr_n_u32(vreinterpret_u32_u8(v01), 6));
uint8x8_t v20 = vreinterpret_u8_u32(
vshr_n_u32(vreinterpret_u32_u8(v00), 12));
uint8x8_t v21 = vreinterpret_u8_u32(
vshr_n_u32(vreinterpret_u32_u8(v01), 12));
uint8x8_t v30 = vreinterpret_u8_u32(
vshr_n_u32(vreinterpret_u32_u8(v00), 18));
uint8x8_t v31 = vreinterpret_u8_u32(
vshr_n_u32(vreinterpret_u32_u8(v01), 18));
uint8x8x4_t vtbl0 = {{ v00, v01, v10, v11 }};
uint8x8x4_t vtbl1 = {{ v20, v21, v30, v31 }};
v00 = vtbl4_u8(vtbl0, vidx1);
v01 = vtbl4_u8(vtbl1, vidx1);
uint8x16_t v0 = vandq_u8(vcombine_u8(v00, v01), vmask);
/* Sum across vector. */
uint64x2_t v1 = vpaddlq_u32(vpaddlq_u16(vpaddlq_u8(v0)));
payload_size = vget_lane_u64(vadd_u64(vget_low_u64(v1),
vget_high_u64(v1)), 0);
/* Number of subblocks of size 1. */
v0 = vceqq_u8(v0, vones);
v0 = vandq_u8(v0, vones);
v1 = vpaddlq_u32(vpaddlq_u16(vpaddlq_u8(v0)));
uint32_t nr_ones = vget_lane_u64(vadd_u64(vget_low_u64(v1),
vget_high_u64(v1)), 0);
/* Payload size already stores subblocks of size 1. Fix-up the sum
* using the number of such subblocks. */
payload_size += nr_ones * (uncompressed_size - 1);
payload_size = ALIGN_POT(payload_size, 16);
}
layout[i].size = payload_size;
layout[i].offset = body_size;
body_size += payload_size;
}
return body_size;
no_neon:
#endif
/* Generic version. */
/* XXX: It might be faster to copy the header from non-cacheable memory
* into a cacheline sized chunk in cacheable memory in order to avoid too
* many uncached transactions. Not sure though, so it needs testing. */
for (uint32_t i = 0; i < nr_blocks; i++) {
uint32_t payload_size =
pan_afbc_payload_size(arch, headers[i], uncompressed_size);
layout[i].size = payload_size;
layout[i].offset = body_size;
body_size += payload_size;
}
return body_size;
}
#endif
+901
View File
@@ -0,0 +1,901 @@
/*
* Copyright (C) 2008 VMware, Inc.
* Copyright (C) 2014 Broadcom
* Copyright (C) 2018-2019 Alyssa Rosenzweig
* Copyright (C) 2019-2020 Collabora, Ltd.
* Copyright (C) 2026 Arm Ltd.
* SPDX-License-Identifier: MIT
*/
#ifndef __PAN_AFBC_H
#define __PAN_AFBC_H
#include "pan_format.h"
#include "pan_layout.h"
#include "drm-uapi/drm_fourcc.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Arm FrameBuffer Compression (AFBC) is a lossless compression scheme natively
* implemented in Mali GPUs (as well as many display controllers paired with
* Mali GPUs, etc). Where possible, Panfrost prefers to use AFBC for both
* rendering and texturing. In most cases, this is a performance-win due to a
* dramatic reduction in memory bandwidth and cache locality compared to a
* linear resources.
*
* AFBC divides the framebuffer into 16x16 tiles (other sizes possible, TODO:
* do we need to support this?). So, the width and height each must be aligned
* up to 16 pixels. This is inherently good for performance; note that for a 4
* byte-per-pixel format like RGBA8888, that means that rows are 16*4=64 byte
* aligned, which is the cache-line size.
*
* For each AFBC-compressed resource, there is a single contiguous
* (CPU/GPU-shared) buffer. This buffer itself is divided into two parts:
* header and body, placed immediately after each other.
*
* The AFBC header contains 16 bytes of metadata per tile.
*
* The AFBC body is the same size as the original linear resource (padded to
* the nearest tile). Although the body comes immediately after the header, it
* must also be cache-line aligned, so there can sometimes be a bit of padding
* between the header and body.
*
* As an example, a 64x64 RGBA framebuffer contains 64/16 = 4 tiles horizontally
* and 4 tiles vertically. There are 4*4=16 tiles in total, each containing 16
* bytes of metadata, so there is a 16*16=256 byte header. 64x64 is already
* tile aligned, so the body is 64*64 * 4 bytes per pixel = 16384 bytes of
* body.
*
* From userspace, Panfrost needs to be able to calculate these sizes. It
* explicitly does not and can not know the format of the data contained within
* this header and body. The GPU has native support for AFBC encode/decode. For
* an internal FBO or a framebuffer used for scanout with an AFBC-compatible
* winsys/display-controller, the buffer is maintained AFBC throughout flight,
* and the driver never needs to know the internal data. For edge cases where
* the driver really does need to read/write from the AFBC resource, we
* generate a linear staging buffer and use the GPU to blit AFBC<--->linear.
*/
#define AFBC_HEADER_BYTES_PER_TILE 16
/* AFBC format mode. The ordering is intended to match the Valhall hardware enum
* ("AFBC Compression Mode"), but this enum is required in software on older
* hardware for correct handling of texture views. Defining the enum lets us
* unify these code paths.
*/
enum pan_afbc_mode {
PAN_AFBC_MODE_R8,
PAN_AFBC_MODE_R8G8,
PAN_AFBC_MODE_R5G6B5,
PAN_AFBC_MODE_R4G4B4A4,
PAN_AFBC_MODE_R5G5B5A1,
PAN_AFBC_MODE_R8G8B8,
PAN_AFBC_MODE_R8G8B8A8,
PAN_AFBC_MODE_R10G10B10A2,
PAN_AFBC_MODE_R11G11B10,
/* 16 bit modes, available on v10+ */
PAN_AFBC_MODE_R16,
PAN_AFBC_MODE_R16G16,
PAN_AFBC_MODE_R16G16B16,
PAN_AFBC_MODE_R16G16B16A16,
/* YUV special modes */
PAN_AFBC_MODE_YUV420_6C8,
PAN_AFBC_MODE_YUV420_2C8,
PAN_AFBC_MODE_YUV420_1C8,
PAN_AFBC_MODE_YUV420_6C10,
PAN_AFBC_MODE_YUV420_2C10,
PAN_AFBC_MODE_YUV420_1C10,
PAN_AFBC_MODE_YUV422_4C8,
PAN_AFBC_MODE_YUV422_2C8,
PAN_AFBC_MODE_YUV422_1C8,
PAN_AFBC_MODE_YUV422_4C10,
PAN_AFBC_MODE_YUV422_2C10,
PAN_AFBC_MODE_YUV422_1C10,
/* Sentintel signalling a format that cannot be compressed */
PAN_AFBC_MODE_INVALID
};
/*
* An AFBC header block provides access to an associated superblock payload of
* 4x4 subblocks or to an embedded solid color.
*/
struct pan_afbc_headerblock {
union {
/* Superblock payload. */
struct {
/* Offset in bytes from the start of the AFBC buffer (1st header
* block) to the start of the superblock payload data. */
uint32_t offset;
/* Sizes in bytes of the 4x4 6-bit subblocks. */
uint8_t subblock_sizes[12];
} payload;
/* Solid color. */
struct {
uint64_t reserved;
/* RGBA 8-8-8-8 color format. */
/* XXX: Add other formats. */
struct {
uint8_t r, g, b, a;
uint32_t reserved;
} rgba8888;
} color;
/* Random access. */
uint8_t u8[16];
uint16_t u16[8];
uint32_t u32[4];
uint64_t u64[2];
};
};
/*
* An AFBC payload extent describes the extent of the payload data (compressed
* superblock data) associated to a pan_afbc_headerblock.
*/
struct pan_afbc_payload_extent {
uint32_t size;
uint32_t offset;
};
/*
* Given an AFBC modifier, return the superblock size.
*
* We do not yet have any use cases for multiplanar YCBCr formats with different
* superblock sizes on the luma and chroma planes. These formats are unsupported
* for now.
*/
static inline struct pan_image_block_size
pan_afbc_superblock_size(uint64_t modifier)
{
unsigned index = (modifier & AFBC_FORMAT_MOD_BLOCK_SIZE_MASK);
assert(drm_is_afbc(modifier));
switch (index) {
case AFBC_FORMAT_MOD_BLOCK_SIZE_16x16:
return (struct pan_image_block_size){16, 16};
case AFBC_FORMAT_MOD_BLOCK_SIZE_32x8:
return (struct pan_image_block_size){32, 8};
case AFBC_FORMAT_MOD_BLOCK_SIZE_64x4:
return (struct pan_image_block_size){64, 4};
default:
assert(!"Unsupported AFBC block size");
return (struct pan_image_block_size){0, 0};
}
}
/* Same as pan_afbc_superblock_size_el() but counted in block elements
* instead of pixels. For anything non-YUV this is the same. */
static inline struct pan_image_block_size
pan_afbc_superblock_size_el(enum pipe_format format, uint64_t modifier)
{
struct pan_image_block_size sb_size_px = pan_afbc_superblock_size(modifier);
assert(sb_size_px.width % util_format_get_blockwidth(format) == 0);
assert(sb_size_px.height % util_format_get_blockheight(format) == 0);
return (struct pan_image_block_size){
.width = sb_size_px.width / util_format_get_blockwidth(format),
.height = sb_size_px.height / util_format_get_blockheight(format),
};
}
/*
* Given an AFBC modifier, return the render size.
*/
static inline struct pan_image_block_size
pan_afbc_renderblock_size(uint64_t modifier)
{
struct pan_image_block_size blk_size = pan_afbc_superblock_size(modifier);
/* The GPU needs to render 16x16 tiles. For wide tiles, that means we
* have to extend the render region to have a height of 16 pixels.
*/
blk_size.height = ALIGN_POT(blk_size.height, 16);
return blk_size;
}
/* Same as pan_afbc_renderblock_size() but counted in block elements
* instead of pixels. For anything non-YUV this is the same. */
static inline struct pan_image_block_size
pan_afbc_renderblock_size_el(enum pipe_format format, uint64_t modifier)
{
struct pan_image_block_size rb_size_px = pan_afbc_renderblock_size(modifier);
assert(rb_size_px.width % util_format_get_blockwidth(format) == 0);
assert(rb_size_px.height % util_format_get_blockheight(format) == 0);
return (struct pan_image_block_size){
.width = rb_size_px.width / util_format_get_blockwidth(format),
.height = rb_size_px.height / util_format_get_blockheight(format),
};
}
/*
* Given an AFBC modifier, return the width of the superblock.
*/
static inline unsigned
pan_afbc_superblock_width(uint64_t modifier)
{
return pan_afbc_superblock_size(modifier).width;
}
/*
* Given an AFBC modifier, return the height of the superblock.
*/
static inline unsigned
pan_afbc_superblock_height(uint64_t modifier)
{
return pan_afbc_superblock_size(modifier).height;
}
/*
* Given an AFBC modifier, return if "wide blocks" are used. Wide blocks are
* defined as superblocks wider than 16 pixels, the minimum (and default) super
* block width.
*/
static inline bool
pan_afbc_is_wide(uint64_t modifier)
{
return pan_afbc_superblock_width(modifier) > 16;
}
/*
* Given an AFBC modifier, return the subblock size (subdivision of a
* superblock). This is always 4x4 for now as we only support one AFBC
* superblock layout.
*/
static inline struct pan_image_block_size
pan_afbc_subblock_size(uint64_t modifier)
{
return (struct pan_image_block_size){4, 4};
}
/*
* Given an AFBC header block, return the size of the subblock at the given
* index in the range [0, 15].
*/
static inline unsigned
pan_afbc_header_subblock_size(struct pan_afbc_headerblock header,
uint32_t index)
{
uint64_t mask = BITFIELD_MASK(6);
switch (index) {
case 0: return (header.u64[0] >> 32) & mask; break;
case 1: return (header.u64[0] >> 38) & mask; break;
case 2: return (header.u64[0] >> 44) & mask; break;
case 3: return (header.u64[0] >> 50) & mask; break;
case 4: return (header.u64[0] >> 56) & mask; break;
case 5: return ((header.u64[0] >> 62) |
(header.u64[1] << 2)) & mask; break;
case 6: return (header.u64[1] >> 4) & mask; break;
case 7: return (header.u64[1] >> 10) & mask; break;
case 8: return (header.u64[1] >> 16) & mask; break;
case 9: return (header.u64[1] >> 22) & mask; break;
case 10: return (header.u64[1] >> 28) & mask; break;
case 11: return (header.u64[1] >> 34) & mask; break;
case 12: return (header.u64[1] >> 40) & mask; break;
case 13: return (header.u64[1] >> 46) & mask; break;
case 14: return (header.u64[1] >> 52) & mask; break;
case 15: return (header.u64[1] >> 58) & mask; break;
default: UNREACHABLE("invalid index"); return 0;
}
}
/*
* Given an AFBC header block, return the size in bytes of the associated
* superblock payload data (for the superblock layouts 0, 3, 4 and 7).
*/
static inline uint32_t
pan_afbc_payload_size(unsigned arch,
struct pan_afbc_headerblock header,
uint32_t uncompressed_size)
{
/* Skip sum if the 1st subblock is 0 (solid color encoding). */
if (arch >= 7 && pan_afbc_header_subblock_size(header, 0) == 0)
return 0;
uint64_t size = 0;
for (unsigned i = 0; i < 16; i++) {
unsigned sub_size = pan_afbc_header_subblock_size(header, i);
size += sub_size != 1 ? sub_size : uncompressed_size;
}
return ALIGN_POT(size, 16);
}
/*
* Given a format and a modifier, return the size in bytes of an uncompressed
* superblock payload.
*/
static inline uint32_t
pan_afbc_payload_uncompressed_size(enum pipe_format format, uint64_t modifier)
{
struct pan_image_block_size size_px = pan_afbc_subblock_size(modifier);
uint32_t size_B = util_format_get_blocksizebits(format) / 8;
size_B *= size_px.width * size_px.height;
assert(size_B == ALIGN_POT(size_B, 16));
return size_B;
}
/*
* Calculate the size of each AFBC superblock payload data from the given
* header blocks, generate a packed AFBC payload layout and return the body
* size.
*/
uint32_t
pan_afbc_payload_layout_packed(unsigned arch,
const struct pan_afbc_headerblock *headers,
struct pan_afbc_payload_extent *layout,
uint32_t nr_blocks, enum pipe_format format,
uint64_t modifier);
static inline uint32_t
pan_afbc_header_row_stride_align(unsigned arch, enum pipe_format format,
uint64_t modifier)
{
if (arch <= 7 || !(modifier & AFBC_FORMAT_MOD_TILED))
return 16;
if (util_format_get_blocksizebits(format) <= 32)
return 1024;
else
return 256;
}
static inline uint32_t
pan_afbc_header_align(unsigned arch, uint64_t modifier)
{
if (modifier & AFBC_FORMAT_MOD_TILED)
return 4096;
else if (arch >= 6)
return 128;
else
return 64;
}
/*
* Determine the required alignment for the body offset of an AFBC image. For
* now, this depends only on whether tiling is in use. These minimum alignments
* are required on all current GPUs.
*/
static inline uint32_t
pan_afbc_body_align(unsigned arch, uint64_t modifier)
{
/* Body and header alignments are actually the same. */
return pan_afbc_header_align(arch, modifier);
}
/* Get the body offset for a given AFBC header size. */
static inline uint32_t
pan_afbc_body_offset(unsigned arch, uint64_t modifier, uint32_t header_size)
{
return ALIGN_POT(header_size, pan_afbc_body_align(arch, modifier));
}
/*
* Determine the tile size used by AFBC. This tiles superblocks themselves.
* Current GPUs support either 8x8 tiling or no tiling (1x1) for most
* formats; but note that formats with more than 32bpp use 4x4 tiling
*/
static inline unsigned
pan_afbc_tile_size(enum pipe_format format, uint64_t modifier)
{
if ( !(modifier & AFBC_FORMAT_MOD_TILED) ) {
return 1;
}
return util_format_get_blocksizebits(format) <= 32 ? 8 : 4;
}
/*
* Determine the number of bytes between header rows for an AFBC image. For an
* image with linear headers, this is simply the number of header blocks
* (=superblocks) per row times the numbers of bytes per header block. For an
* image with tiled headers, this is multipled by the number of rows of
* header blocks are in a tile together.
*/
static inline uint32_t
pan_afbc_row_stride(enum pipe_format format, uint64_t modifier, uint32_t width)
{
unsigned block_width = pan_afbc_superblock_width(modifier);
return (width / block_width) * pan_afbc_tile_size(format, modifier) *
AFBC_HEADER_BYTES_PER_TILE;
}
/*
* Determine the number of header blocks between header rows. This is equal to
* the number of bytes between header rows divided by the bytes per blocks of a
* header tile. This is also divided by the tile size to give a "line stride" in
* blocks, rather than a real row stride. This is required by Bifrost.
*/
static inline uint32_t
pan_afbc_stride_blocks(enum pipe_format format, uint64_t modifier, uint32_t row_stride_bytes)
{
return row_stride_bytes /
(AFBC_HEADER_BYTES_PER_TILE * pan_afbc_tile_size(format, modifier));
}
/* Returns a height in superblocks taking into account the tile alignment
* requirement coming from the modifier.
*/
static inline uint32_t
pan_afbc_height_blocks(enum pipe_format format, uint64_t modifier, uint32_t height_px)
{
return ALIGN_POT(
DIV_ROUND_UP(height_px, pan_afbc_superblock_height(modifier)),
pan_afbc_tile_size(format, modifier));
}
static inline enum pipe_format
pan_afbc_unswizzled_format(unsigned arch, enum pipe_format format)
{
switch (format) {
case PIPE_FORMAT_A8_UNORM:
case PIPE_FORMAT_L8_UNORM:
case PIPE_FORMAT_I8_UNORM:
return PIPE_FORMAT_R8_UNORM;
case PIPE_FORMAT_L8A8_UNORM:
return PIPE_FORMAT_R8G8_UNORM;
case PIPE_FORMAT_B8G8R8_UNORM:
return PIPE_FORMAT_R8G8B8_UNORM;
case PIPE_FORMAT_R8G8B8X8_UNORM:
case PIPE_FORMAT_B8G8R8A8_UNORM:
case PIPE_FORMAT_B8G8R8X8_UNORM:
return PIPE_FORMAT_R8G8B8A8_UNORM;
case PIPE_FORMAT_A8R8G8B8_UNORM:
case PIPE_FORMAT_X8R8G8B8_UNORM:
case PIPE_FORMAT_X8B8G8R8_UNORM:
case PIPE_FORMAT_A8B8G8R8_UNORM:
/* v7 can only support AFBC for RGB and BGR */
return arch == 7 ? format : PIPE_FORMAT_R8G8B8A8_UNORM;
case PIPE_FORMAT_B5G6R5_UNORM:
return PIPE_FORMAT_R5G6B5_UNORM;
case PIPE_FORMAT_B5G5R5A1_UNORM:
return PIPE_FORMAT_R5G5B5A1_UNORM;
case PIPE_FORMAT_R10G10B10X2_UNORM:
case PIPE_FORMAT_B10G10R10A2_UNORM:
case PIPE_FORMAT_B10G10R10X2_UNORM:
return PIPE_FORMAT_R10G10B10A2_UNORM;
case PIPE_FORMAT_B4G4R4A4_UNORM:
return PIPE_FORMAT_R4G4B4A4_UNORM;
case PIPE_FORMAT_A4B4G4R4_UNORM:
/* v7 can only support AFBC for RGB and BGR */
return arch == 7 ? format : PIPE_FORMAT_R4G4B4A4_UNORM;
default:
return format;
}
}
/* AFBC supports compressing a few canonical formats. Additional formats are
* available by using a canonical internal format. Given a PIPE format, find
* the canonical AFBC internal format if it exists, or NONE if the format
* cannot be compressed. */
static inline enum pan_afbc_mode
pan_afbc_format(unsigned arch, enum pipe_format format, unsigned plane_idx)
{
assert(plane_idx < util_format_get_num_planes(format));
switch (format) {
case PIPE_FORMAT_R8_G8B8_420_UNORM:
case PIPE_FORMAT_R8_B8G8_420_UNORM:
return plane_idx == 0 ? PAN_AFBC_MODE_YUV420_1C8
: PAN_AFBC_MODE_YUV420_2C8;
case PIPE_FORMAT_R8_G8_B8_420_UNORM:
case PIPE_FORMAT_R8_B8_G8_420_UNORM:
return PAN_AFBC_MODE_YUV420_1C8;
case PIPE_FORMAT_R8_G8B8_422_UNORM:
return plane_idx == 0 ? PAN_AFBC_MODE_YUV422_1C8
: PAN_AFBC_MODE_YUV422_2C8;
case PIPE_FORMAT_R10_G10B10_420_UNORM:
return plane_idx == 0 ? PAN_AFBC_MODE_YUV420_1C10
: PAN_AFBC_MODE_YUV420_2C10;
case PIPE_FORMAT_R10_G10B10_422_UNORM:
return plane_idx == 0 ? PAN_AFBC_MODE_YUV422_1C10
: PAN_AFBC_MODE_YUV422_2C10;
case PIPE_FORMAT_R8G8B8_420_UNORM_PACKED:
return PAN_AFBC_MODE_YUV420_6C8;
case PIPE_FORMAT_R10G10B10_420_UNORM_PACKED:
return PAN_AFBC_MODE_YUV420_6C10;
case PIPE_FORMAT_R8G8_R8B8_UNORM:
case PIPE_FORMAT_R8B8_R8G8_UNORM:
return PAN_AFBC_MODE_YUV422_4C8;
case PIPE_FORMAT_X6R10X6G10_X6R10X6B10_422_UNORM:
return PAN_AFBC_MODE_YUV422_4C10;
default:
break;
}
/* sRGB does not change the pixel format itself, only the
* interpretation. The interpretation is handled by conversion hardware
* independent to the compression hardware, so we can compress sRGB
* formats by using the corresponding linear format.
*/
format = util_format_linear(format);
/* AFBC is type agnostic on v9+, so the following logic is done on UNORM
* for simplicity, so make sure we convert to _UNORM first.
*/
if (arch >= 9)
format = util_format_any_to_unorm(format);
/* Luminance-alpha not supported for AFBC on v7+ */
/* 16 bit modes only available for AFBC on v10 and later */
switch (format) {
case PIPE_FORMAT_A8_UNORM:
case PIPE_FORMAT_L8_UNORM:
case PIPE_FORMAT_I8_UNORM:
case PIPE_FORMAT_L8A8_UNORM:
if (arch >= 7)
return PAN_AFBC_MODE_INVALID;
else
break;
case PIPE_FORMAT_R16_UNORM:
case PIPE_FORMAT_R16G16_UNORM:
case PIPE_FORMAT_R16G16B16_UNORM:
case PIPE_FORMAT_R16G16B16A16_UNORM:
if (arch < 10)
return PAN_AFBC_MODE_INVALID;
else
break;
case PIPE_FORMAT_S8_UINT:
case PIPE_FORMAT_R11G11B10_FLOAT:
if (arch < 9)
return PAN_AFBC_MODE_INVALID;
else
break;
default:
break;
}
/* We handle swizzling orthogonally to AFBC */
format = pan_afbc_unswizzled_format(arch, format);
/* clang-format off */
switch (format) {
case PIPE_FORMAT_R8_UNORM: return PAN_AFBC_MODE_R8;
case PIPE_FORMAT_R8G8_UNORM: return PAN_AFBC_MODE_R8G8;
case PIPE_FORMAT_R8G8B8_UNORM: return PAN_AFBC_MODE_R8G8B8;
case PIPE_FORMAT_R8G8B8A8_UNORM: return PAN_AFBC_MODE_R8G8B8A8;
case PIPE_FORMAT_R5G6B5_UNORM: return PAN_AFBC_MODE_R5G6B5;
case PIPE_FORMAT_R5G5B5A1_UNORM: return PAN_AFBC_MODE_R5G5B5A1;
case PIPE_FORMAT_R10G10B10A2_UNORM: return PAN_AFBC_MODE_R10G10B10A2;
case PIPE_FORMAT_R4G4B4A4_UNORM: return PAN_AFBC_MODE_R4G4B4A4;
case PIPE_FORMAT_Z16_UNORM: return PAN_AFBC_MODE_R8G8;
case PIPE_FORMAT_Z32_FLOAT: return PAN_AFBC_MODE_R8G8B8A8;
case PIPE_FORMAT_Z24_UNORM_S8_UINT: return PAN_AFBC_MODE_R8G8B8A8;
case PIPE_FORMAT_Z24X8_UNORM: return PAN_AFBC_MODE_R8G8B8A8;
case PIPE_FORMAT_X24S8_UINT: return PAN_AFBC_MODE_R8G8B8A8;
case PIPE_FORMAT_Z24_UNORM_PACKED: return PAN_AFBC_MODE_R8G8B8;
case PIPE_FORMAT_S8_UINT: return PAN_AFBC_MODE_R8;
case PIPE_FORMAT_R11G11B10_FLOAT: return PAN_AFBC_MODE_R11G11B10;
case PIPE_FORMAT_R16_UNORM: return PAN_AFBC_MODE_R16;
case PIPE_FORMAT_R16G16_UNORM: return PAN_AFBC_MODE_R16G16;
case PIPE_FORMAT_R16G16B16_UNORM: return PAN_AFBC_MODE_R16G16B16;
case PIPE_FORMAT_R16G16B16A16_UNORM:
return PAN_AFBC_MODE_R16G16B16A16;
default: return PAN_AFBC_MODE_INVALID;
}
/* clang-format on */
}
/* A format may be compressed as AFBC if it has an AFBC internal format */
static inline bool
pan_afbc_supports_format(unsigned arch, enum pipe_format format)
{
unsigned plane_count = util_format_get_num_planes(format);
for (unsigned i = 0; i < plane_count; i++) {
if (pan_afbc_format(arch, format, i) == PAN_AFBC_MODE_INVALID)
return false;
}
return true;
}
/* The lossless colour transform (AFBC_FORMAT_MOD_YTR) requires RGB. */
static inline bool
pan_afbc_can_ytr(enum pipe_format format)
{
const struct util_format_description *desc = util_format_description(format);
/* YTR is only defined for RGB(A) */
if (desc->nr_channels != 3 && desc->nr_channels != 4)
return false;
/* The fourth channel if it exists doesn't matter */
return desc->colorspace == UTIL_FORMAT_COLORSPACE_RGB;
}
static inline bool
pan_afbc_check_params(enum pan_afbc_mode mode, uint64_t modifier)
{
unsigned block_width = pan_afbc_superblock_width(modifier);
bool is_split = modifier & AFBC_FORMAT_MOD_SPLIT;
/* YTR is not useful on RGB formats, nor with one- or two-channel
* formats */
bool is_ytr = modifier & AFBC_FORMAT_MOD_YTR;
switch (mode) {
case PAN_AFBC_MODE_YUV420_6C8:
case PAN_AFBC_MODE_YUV420_2C8:
case PAN_AFBC_MODE_YUV420_1C8:
case PAN_AFBC_MODE_YUV420_6C10:
case PAN_AFBC_MODE_YUV420_2C10:
case PAN_AFBC_MODE_YUV420_1C10:
case PAN_AFBC_MODE_YUV422_4C8:
case PAN_AFBC_MODE_YUV422_2C8:
case PAN_AFBC_MODE_YUV422_1C8:
case PAN_AFBC_MODE_YUV422_4C10:
case PAN_AFBC_MODE_YUV422_2C10:
case PAN_AFBC_MODE_YUV422_1C10:
case PAN_AFBC_MODE_R8:
case PAN_AFBC_MODE_R8G8:
case PAN_AFBC_MODE_R16:
case PAN_AFBC_MODE_R16G16:
if (is_ytr)
return false;
break;
default:
break;
}
/* 16x16 superblocks are universally compatible */
if (block_width == 16)
return true;
/* 64x4 blocks are only usable for multi-planar YUV formats,
* and not in block-split mode. */
if (block_width == 64) {
switch (mode) {
case PAN_AFBC_MODE_YUV420_2C8:
case PAN_AFBC_MODE_YUV420_1C8:
case PAN_AFBC_MODE_YUV420_2C10:
case PAN_AFBC_MODE_YUV420_1C10:
case PAN_AFBC_MODE_YUV422_2C8:
case PAN_AFBC_MODE_YUV422_1C8:
case PAN_AFBC_MODE_YUV422_2C10:
case PAN_AFBC_MODE_YUV422_1C10:
return !is_split;
default:
return false;
}
}
assert(block_width == 32);
/* 32x8 superblocks have different requirements based on the format:
* block-split mode is required for effective bpp >16, and prohibited
* for effective bpp <= 16. */
switch (mode) {
case PAN_AFBC_MODE_R8G8B8:
case PAN_AFBC_MODE_R8G8B8A8:
case PAN_AFBC_MODE_R10G10B10A2:
case PAN_AFBC_MODE_R11G11B10:
case PAN_AFBC_MODE_R16G16:
case PAN_AFBC_MODE_R16G16B16:
case PAN_AFBC_MODE_R16G16B16A16:
return is_split;
case PAN_AFBC_MODE_R8:
case PAN_AFBC_MODE_R8G8:
case PAN_AFBC_MODE_R5G6B5:
case PAN_AFBC_MODE_R4G4B4A4:
case PAN_AFBC_MODE_R5G5B5A1:
case PAN_AFBC_MODE_R16:
case PAN_AFBC_MODE_YUV420_6C8:
case PAN_AFBC_MODE_YUV420_6C10:
case PAN_AFBC_MODE_YUV422_4C8:
case PAN_AFBC_MODE_YUV422_4C10:
return !is_split;
/* The following split luma/chroma layouts require
* AFBC_FORMAT_MOD_BLOCK_SIZE_32x8_64x4 to be usable, since the luma
* planes require non-split 32x8, and the chroma planes require 64x4.
*/
case PAN_AFBC_MODE_YUV420_1C8:
case PAN_AFBC_MODE_YUV420_1C10:
case PAN_AFBC_MODE_YUV422_1C8:
case PAN_AFBC_MODE_YUV422_1C10:
return !is_split;
case PAN_AFBC_MODE_YUV420_2C8:
case PAN_AFBC_MODE_YUV420_2C10:
case PAN_AFBC_MODE_YUV422_2C8:
case PAN_AFBC_MODE_YUV422_2C10:
return false;
default:
assert(!"unknown mode in pan_afbc_check_params()");
return false;
}
}
/* Only support packing for RGB formats for now. */
static inline bool
pan_afbc_can_pack(enum pipe_format format)
{
const struct util_format_description *desc = util_format_description(format);
return desc->colorspace == UTIL_FORMAT_COLORSPACE_RGB;
}
/*
* Check if a gen supports AFBC with tiled headers (and hence also solid
* colour blocks).
*/
static inline bool
pan_afbc_can_tile(unsigned arch)
{
return arch >= 7;
}
/*
* Check if a gen supports AFBC with split-block mode.
*/
static inline bool
pan_afbc_can_split(unsigned arch)
{
return arch >= 6;
}
#if PAN_ARCH >= 9
static inline enum mali_afbc_compression_mode
pan_afbc_compression_mode(enum pan_afbc_mode mode)
{
/* Map canonical formats to the hardware enum. This only
* needs to handle the subset of formats returned by
* pan_afbc_format.
*/
switch (mode) {
case PAN_AFBC_MODE_R8:
return MALI_AFBC_COMPRESSION_MODE_R8;
case PAN_AFBC_MODE_R8G8:
return MALI_AFBC_COMPRESSION_MODE_R8G8;
case PAN_AFBC_MODE_R5G6B5:
return MALI_AFBC_COMPRESSION_MODE_R5G6B5;
case PAN_AFBC_MODE_R4G4B4A4:
return MALI_AFBC_COMPRESSION_MODE_R4G4B4A4;
case PAN_AFBC_MODE_R5G5B5A1:
return MALI_AFBC_COMPRESSION_MODE_R5G5B5A1;
case PAN_AFBC_MODE_R8G8B8:
return MALI_AFBC_COMPRESSION_MODE_R8G8B8;
case PAN_AFBC_MODE_R8G8B8A8:
return MALI_AFBC_COMPRESSION_MODE_R8G8B8A8;
case PAN_AFBC_MODE_R10G10B10A2:
return MALI_AFBC_COMPRESSION_MODE_R10G10B10A2;
case PAN_AFBC_MODE_R11G11B10:
return MALI_AFBC_COMPRESSION_MODE_R11G11B10;
#if PAN_ARCH >= 10
case PAN_AFBC_MODE_R16:
return MALI_AFBC_COMPRESSION_MODE_R16;
case PAN_AFBC_MODE_R16G16:
return MALI_AFBC_COMPRESSION_MODE_R16G16;
case PAN_AFBC_MODE_R16G16B16:
return MALI_AFBC_COMPRESSION_MODE_R16G16B16;
case PAN_AFBC_MODE_R16G16B16A16:
return MALI_AFBC_COMPRESSION_MODE_R16G16B16A16;
#endif
#if PAN_ARCH >= 14
case PAN_AFBC_MODE_YUV420_6C8:
return MALI_AFBC_COMPRESSION_MODE_Y8U8V8_420;
case PAN_AFBC_MODE_YUV420_2C8:
return MALI_AFBC_COMPRESSION_MODE_R8G8;
case PAN_AFBC_MODE_YUV420_1C8:
return MALI_AFBC_COMPRESSION_MODE_R8;
case PAN_AFBC_MODE_YUV420_6C10:
return MALI_AFBC_COMPRESSION_MODE_Y10U10V10_420;
case PAN_AFBC_MODE_YUV420_2C10:
return MALI_AFBC_COMPRESSION_MODE_R10G10;
case PAN_AFBC_MODE_YUV420_1C10:
return MALI_AFBC_COMPRESSION_MODE_R10;
case PAN_AFBC_MODE_YUV422_4C8:
return MALI_AFBC_COMPRESSION_MODE_Y8U8Y8V8_422;
case PAN_AFBC_MODE_YUV422_2C8:
return MALI_AFBC_COMPRESSION_MODE_R8G8;
case PAN_AFBC_MODE_YUV422_1C8:
return MALI_AFBC_COMPRESSION_MODE_R8;
case PAN_AFBC_MODE_YUV422_4C10:
return MALI_AFBC_COMPRESSION_MODE_Y10U10Y10V10_422;
case PAN_AFBC_MODE_YUV422_2C10:
return MALI_AFBC_COMPRESSION_MODE_R10G10;
case PAN_AFBC_MODE_YUV422_1C10:
return MALI_AFBC_COMPRESSION_MODE_R10;
#else
case PAN_AFBC_MODE_YUV420_6C8:
return MALI_AFBC_COMPRESSION_MODE_YUV420_6C8;
case PAN_AFBC_MODE_YUV420_2C8:
return MALI_AFBC_COMPRESSION_MODE_YUV420_2C8;
case PAN_AFBC_MODE_YUV420_1C8:
return MALI_AFBC_COMPRESSION_MODE_YUV420_1C8;
case PAN_AFBC_MODE_YUV420_6C10:
return MALI_AFBC_COMPRESSION_MODE_YUV420_6C10;
case PAN_AFBC_MODE_YUV420_2C10:
return MALI_AFBC_COMPRESSION_MODE_YUV420_2C10;
case PAN_AFBC_MODE_YUV420_1C10:
return MALI_AFBC_COMPRESSION_MODE_YUV420_1C10;
case PAN_AFBC_MODE_YUV422_4C8:
return MALI_AFBC_COMPRESSION_MODE_YUV422_4C8;
case PAN_AFBC_MODE_YUV422_2C8:
return MALI_AFBC_COMPRESSION_MODE_YUV422_2C8;
case PAN_AFBC_MODE_YUV422_1C8:
return MALI_AFBC_COMPRESSION_MODE_YUV422_1C8;
case PAN_AFBC_MODE_YUV422_4C10:
return MALI_AFBC_COMPRESSION_MODE_YUV422_4C10;
case PAN_AFBC_MODE_YUV422_2C10:
return MALI_AFBC_COMPRESSION_MODE_YUV422_2C10;
case PAN_AFBC_MODE_YUV422_1C10:
return MALI_AFBC_COMPRESSION_MODE_YUV422_1C10;
#endif /* PAN_ARCH >= 14 */
#if PAN_ARCH == 9
case PAN_AFBC_MODE_R16:
case PAN_AFBC_MODE_R16G16:
case PAN_AFBC_MODE_R16G16B16:
case PAN_AFBC_MODE_R16G16B16A16:
#endif
case PAN_AFBC_MODE_INVALID:
UNREACHABLE("Invalid AFBC format");
}
UNREACHABLE("all AFBC formats handled");
}
static inline enum mali_afbc_compression_mode
pan_afbc_decompression_mode(enum pipe_format view_format,
enum pan_afbc_mode img_mode)
{
/* There are special cases for texture views of stencil images. */
if (img_mode == PAN_AFBC_MODE_R8G8B8A8 &&
view_format == PIPE_FORMAT_X24S8_UINT)
return MALI_AFBC_COMPRESSION_MODE_X24S8;
else if (view_format == PIPE_FORMAT_S8_UINT)
return MALI_AFBC_COMPRESSION_MODE_S8;
return pan_afbc_compression_mode(img_mode);
}
#endif
static inline bool
format_requires_afbc(enum pipe_format format) {
switch (format) {
case PIPE_FORMAT_R8G8B8_420_UNORM_PACKED:
case PIPE_FORMAT_R10G10B10_420_UNORM_PACKED:
case PIPE_FORMAT_X6R10X6G10_X6R10X6B10_422_UNORM:
return true;
default:
return false;
}
}
#ifdef __cplusplus
} /* extern C */
#endif
#endif
+448
View File
@@ -0,0 +1,448 @@
/*
* Copyright (C) 2023 Collabora, Ltd.
* Copyright (C) 2026 Arm Ltd.
* SPDX-License-Identifier: MIT
*/
#ifndef __PAN_AFRC_H
#define __PAN_AFRC_H
#include "pan_format.h"
#include "pan_layout.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Arm Fixed-Rate Compression (AFRC) is a lossy compression scheme natively
* implemented in Mali GPUs. AFRC images can only be rendered or textured
* from. It is currently not possible to do image reads or writes to such
* resources.
*
* AFRC divides the image into an array of fixed-size coding units which are
* grouped into paging tiles. The size of the coding units (clump size)
* depends on the image format and the pixel layout (whether it is optimized
* for 2D locality and rotation, or for scan line order access). The last
* parameter is the size of the compressed block that can be either 16, 24,
* or 32 bytes.
*
* The compression rate can be calculated by dividing the compressed block
* size by the uncompressed block size (clump size multiplied by the component
* size and the number of components).
*/
#define AFRC_CLUMPS_PER_TILE 64
enum pan_afrc_rate {
PAN_AFRC_RATE_NONE,
PAN_AFRC_RATE_1BPC,
PAN_AFRC_RATE_2BPC,
PAN_AFRC_RATE_3BPC,
PAN_AFRC_RATE_4BPC,
PAN_AFRC_RATE_5BPC,
PAN_AFRC_RATE_6BPC,
PAN_AFRC_RATE_7BPC,
PAN_AFRC_RATE_8BPC,
PAN_AFRC_RATE_9BPC,
PAN_AFRC_RATE_10BPC,
PAN_AFRC_RATE_11BPC,
PAN_AFRC_RATE_12BPC,
PAN_AFRC_RATE_DEFAULT = 0xF
};
enum pan_afrc_interchange_format {
PAN_AFRC_ICHANGE_FORMAT_RAW,
PAN_AFRC_ICHANGE_FORMAT_YUV444,
PAN_AFRC_ICHANGE_FORMAT_YUV422,
PAN_AFRC_ICHANGE_FORMAT_YUV420,
};
struct pan_afrc_format_info {
unsigned bpc : 4;
unsigned num_comps : 3;
unsigned ichange_fmt : 2;
unsigned num_planes : 2;
};
/*
* Given an AFRC modifier, return whether the layout is optimized for scan
* order (vs rotation order).
*/
static inline bool
pan_afrc_is_scan(uint64_t modifier)
{
return modifier & AFRC_FORMAT_MOD_LAYOUT_SCAN;
}
static inline struct pan_afrc_format_info
pan_afrc_get_format_info(enum pipe_format format)
{
const struct util_format_description *desc = util_format_description(format);
struct pan_afrc_format_info info = {0};
/* No AFRC(compressed) */
if (util_format_is_compressed(format))
return info;
/* No AFRC(ZS). */
if (desc->colorspace == UTIL_FORMAT_COLORSPACE_ZS)
return info;
/* No AFRC(YUV) yet. */
if (pan_format_is_yuv(format))
return info;
unsigned bpc = 0;
for (unsigned c = 0; c < desc->nr_channels; c++) {
if (bpc && bpc != desc->channel[c].size)
return info;
bpc = desc->channel[0].size;
}
info.bpc = bpc;
assert(desc->colorspace == UTIL_FORMAT_COLORSPACE_RGB ||
desc->colorspace == UTIL_FORMAT_COLORSPACE_SRGB);
info.ichange_fmt = PAN_AFRC_ICHANGE_FORMAT_RAW;
info.num_planes = util_format_get_num_planes(format);
info.num_comps = util_format_get_nr_components(format);
return info;
}
struct pan_afrc_block_size {
unsigned size; /* Block size in bytes */
unsigned alignment; /* Buffer alignment */
uint64_t modifier_flag; /* Part of the modifier for CU size */
};
#define BLOCK_SIZE(block_size, buffer_alignment) \
{ \
.size = block_size, \
.alignment = buffer_alignment, \
.modifier_flag = AFRC_FORMAT_MOD_CU_SIZE_##block_size, \
}
#define AFRC_BLOCK_SIZES(__name) \
static const struct pan_afrc_block_size __name[] = { \
BLOCK_SIZE(16, 1024), \
BLOCK_SIZE(24, 512), \
BLOCK_SIZE(32, 2048), \
}
static inline struct pan_image_block_size
pan_afrc_clump_size(enum pipe_format format, bool scan)
{
struct pan_afrc_format_info finfo = pan_afrc_get_format_info(format);
switch (finfo.num_comps) {
case 1:
return scan ? (struct pan_image_block_size){16, 4}
: (struct pan_image_block_size){8, 8};
case 2:
return (struct pan_image_block_size){8, 4};
case 3:
case 4:
return (struct pan_image_block_size){4, 4};
default:
assert(0);
return (struct pan_image_block_size){0, 0};
}
}
/* Total number of components in a AFRC coding unit */
static inline unsigned
pan_afrc_clump_get_nr_components(enum pipe_format format, bool scan)
{
const struct util_format_description *desc = util_format_description(format);
struct pan_image_block_size clump_sz = pan_afrc_clump_size(format, scan);
return clump_sz.width * clump_sz.height * desc->nr_channels;
}
static inline bool
pan_afrc_supports_format(enum pipe_format format)
{
struct pan_afrc_format_info finfo = pan_afrc_get_format_info(format);
return finfo.num_comps != 0;
}
static inline unsigned
pan_afrc_query_rates(enum pipe_format format, unsigned max, uint32_t *rates)
{
if (!pan_afrc_supports_format(format))
return 0;
unsigned clump_comps = pan_afrc_clump_get_nr_components(format, false);
unsigned nr_rates = 0;
/**
* From EGL_EXT_surface_compression:
*
* "For pixel formats with different number of bits per component, the
* specified fixed-rate compression rate applies to the component with
* the highest number of bits."
*
* We only support formats where all components have the same size for now.
* Let's just use the first component size for calculation.
*/
unsigned uncompressed_rate =
util_format_get_component_bits(format, UTIL_FORMAT_COLORSPACE_RGB, 0);
AFRC_BLOCK_SIZES(block_sizes);
for (unsigned i = 0; i < ARRAY_SIZE(block_sizes); ++i) {
unsigned clump_sz = block_sizes[i].size * 8;
unsigned rate = clump_sz / clump_comps;
if (rate >= uncompressed_rate)
continue;
if (nr_rates < max)
rates[nr_rates] = rate;
nr_rates++;
if (max > 0 && nr_rates == max)
break;
}
return nr_rates;
}
static inline unsigned
pan_afrc_get_modifiers(enum pipe_format format, uint32_t rate, unsigned max,
uint64_t *modifiers)
{
if (!pan_afrc_supports_format(format))
return 0;
/* For now, the number of components in a clump is always the same no
* matter the layout for all supported formats */
unsigned clump_comps = pan_afrc_clump_get_nr_components(format, false);
unsigned count = 0;
/* FIXME Choose a more sensitive default compression rate? */
if (rate == PAN_AFRC_RATE_DEFAULT) {
if (max > 0)
modifiers[0] = DRM_FORMAT_MOD_ARM_AFRC(AFRC_FORMAT_MOD_CU_SIZE_24);
if (max > 1)
modifiers[1] = DRM_FORMAT_MOD_ARM_AFRC(AFRC_FORMAT_MOD_CU_SIZE_24 |
AFRC_FORMAT_MOD_LAYOUT_SCAN);
return 2;
}
AFRC_BLOCK_SIZES(block_sizes);
for (unsigned i = 0; i < ARRAY_SIZE(block_sizes); ++i) {
unsigned clump_sz = block_sizes[i].size * 8;
if (rate == clump_sz / clump_comps) {
for (unsigned scan = 0; scan < 2; ++scan) {
if (count < max) {
modifiers[count] = DRM_FORMAT_MOD_ARM_AFRC(
block_sizes[i].modifier_flag |
(scan ? AFRC_FORMAT_MOD_LAYOUT_SCAN : 0));
}
count++;
}
}
}
return count;
}
static inline unsigned
pan_afrc_block_size_from_modifier(uint64_t modifier)
{
switch (modifier & AFRC_FORMAT_MOD_CU_SIZE_MASK) {
case AFRC_FORMAT_MOD_CU_SIZE_16:
return 16;
case AFRC_FORMAT_MOD_CU_SIZE_24:
return 24;
case AFRC_FORMAT_MOD_CU_SIZE_32:
return 32;
default:
UNREACHABLE("invalid coding unit size flag in modifier");
};
}
static inline uint32_t
pan_afrc_get_rate(enum pipe_format format, uint64_t modifier)
{
if (!drm_is_afrc(modifier) || !pan_afrc_supports_format(format))
return PAN_AFRC_RATE_NONE;
bool scan = pan_afrc_is_scan(modifier);
unsigned block_comps = pan_afrc_clump_get_nr_components(format, scan);
uint32_t block_sz = pan_afrc_block_size_from_modifier(modifier) * 8;
return block_sz / block_comps;
}
static inline struct pan_image_block_size
pan_afrc_layout_size(uint64_t modifier)
{
if (pan_afrc_is_scan(modifier))
return (struct pan_image_block_size){16, 4};
else
return (struct pan_image_block_size){8, 8};
}
static inline struct pan_image_block_size
pan_afrc_tile_size(enum pipe_format format, uint64_t modifier)
{
bool scan = pan_afrc_is_scan(modifier);
struct pan_image_block_size clump_sz = pan_afrc_clump_size(format, scan);
struct pan_image_block_size layout_sz = pan_afrc_layout_size(modifier);
return (struct pan_image_block_size){clump_sz.width * layout_sz.width,
clump_sz.height * layout_sz.height};
}
static inline unsigned
pan_afrc_buffer_alignment_from_modifier(uint64_t modifier)
{
switch (modifier & AFRC_FORMAT_MOD_CU_SIZE_MASK) {
case AFRC_FORMAT_MOD_CU_SIZE_16:
return 1024;
case AFRC_FORMAT_MOD_CU_SIZE_24:
return 512;
case AFRC_FORMAT_MOD_CU_SIZE_32:
return 2048;
default:
UNREACHABLE("invalid coding unit size flag in modifier");
};
}
/*
* Determine the number of bytes between rows of paging tiles in an AFRC image
*/
static inline uint32_t
pan_afrc_row_stride(enum pipe_format format, uint64_t modifier, uint32_t width)
{
struct pan_image_block_size tile_size = pan_afrc_tile_size(format, modifier);
unsigned block_size = pan_afrc_block_size_from_modifier(modifier);
return (width / tile_size.width) * block_size * AFRC_CLUMPS_PER_TILE;
}
#if PAN_ARCH >= 10
static inline enum mali_afrc_format
pan_afrc_format(struct pan_afrc_format_info info, uint64_t modifier,
unsigned plane)
{
bool scan = pan_afrc_is_scan(modifier);
assert(info.bpc == 8 || info.bpc == 10);
assert(info.num_comps > 0 && info.num_comps <= 4);
switch (info.ichange_fmt) {
case PAN_AFRC_ICHANGE_FORMAT_RAW:
assert(plane == 0);
if (info.bpc == 8)
return (scan ? MALI_AFRC_FORMAT_R8_SCAN : MALI_AFRC_FORMAT_R8_ROT) +
(info.num_comps - 1);
assert(info.num_comps == 4);
return (scan ? MALI_AFRC_FORMAT_R10G10B10A10_SCAN
: MALI_AFRC_FORMAT_R10G10B10A10_ROT);
#if PAN_ARCH >= 14
case PAN_AFRC_ICHANGE_FORMAT_YUV444:
case PAN_AFRC_ICHANGE_FORMAT_YUV422:
case PAN_AFRC_ICHANGE_FORMAT_YUV420:
if (info.bpc == 8) {
if (plane == 0 || info.num_planes == 3)
return (scan ? MALI_AFRC_FORMAT_R8_SCAN : MALI_AFRC_FORMAT_R8_ROT);
return (scan ? MALI_AFRC_FORMAT_R8G8_SCAN : MALI_AFRC_FORMAT_R8G8_ROT);
}
if (plane == 0 || info.num_planes == 3)
return (scan ? MALI_AFRC_FORMAT_R10_SCAN : MALI_AFRC_FORMAT_R10_ROT);
assert(info.ichange_fmt == PAN_AFRC_ICHANGE_FORMAT_YUV422 ||
info.ichange_fmt == PAN_AFRC_ICHANGE_FORMAT_YUV420);
return (scan ? MALI_AFRC_FORMAT_R10G10_SCAN
: MALI_AFRC_FORMAT_R10G10_ROT);
#else
case PAN_AFRC_ICHANGE_FORMAT_YUV444:
if (info.bpc == 8) {
if (plane == 0 || info.num_planes == 3)
return (scan ? MALI_AFRC_FORMAT_R8_444_SCAN
: MALI_AFRC_FORMAT_R8_444_ROT);
return (scan ? MALI_AFRC_FORMAT_R8G8_444_SCAN
: MALI_AFRC_FORMAT_R8G8_444_ROT);
}
assert(info.num_planes == 3);
return (scan ? MALI_AFRC_FORMAT_R10_444_SCAN
: MALI_AFRC_FORMAT_R10_444_ROT);
case PAN_AFRC_ICHANGE_FORMAT_YUV422:
if (info.bpc == 8) {
if (plane == 0 || info.num_planes == 3)
return (scan ? MALI_AFRC_FORMAT_R8_422_SCAN
: MALI_AFRC_FORMAT_R8_422_ROT);
return (scan ? MALI_AFRC_FORMAT_R8G8_422_SCAN
: MALI_AFRC_FORMAT_R8G8_422_ROT);
}
if (plane == 0 || info.num_planes == 3)
return (scan ? MALI_AFRC_FORMAT_R10_422_SCAN
: MALI_AFRC_FORMAT_R10_422_ROT);
return (scan ? MALI_AFRC_FORMAT_R10G10_422_SCAN
: MALI_AFRC_FORMAT_R10G10_422_ROT);
case PAN_AFRC_ICHANGE_FORMAT_YUV420:
if (info.bpc == 8) {
if (plane == 0 || info.num_planes == 3)
return (scan ? MALI_AFRC_FORMAT_R8_420_SCAN
: MALI_AFRC_FORMAT_R8_420_ROT);
return (scan ? MALI_AFRC_FORMAT_R8G8_420_SCAN
: MALI_AFRC_FORMAT_R8G8_420_ROT);
}
if (plane == 0 || info.num_planes == 3)
return (scan ? MALI_AFRC_FORMAT_R10_420_SCAN
: MALI_AFRC_FORMAT_R10_420_ROT);
return (scan ? MALI_AFRC_FORMAT_R10G10_420_SCAN
: MALI_AFRC_FORMAT_R10G10_420_ROT);
#endif /* PAN_ARCH >= 14 */
default:
return MALI_AFRC_FORMAT_INVALID;
}
}
static inline enum mali_afrc_block_size
pan_afrc_block_size(uint64_t modifier, unsigned index)
{
/* Clump size flag for planes 1 and 2 is shifted by 4 bits */
unsigned shift = index == 0 ? 0 : 4;
uint64_t flag = (modifier >> shift) & AFRC_FORMAT_MOD_CU_SIZE_MASK;
switch (flag) {
case AFRC_FORMAT_MOD_CU_SIZE_16:
return MALI_AFRC_BLOCK_SIZE_16;
case AFRC_FORMAT_MOD_CU_SIZE_24:
return MALI_AFRC_BLOCK_SIZE_24;
case AFRC_FORMAT_MOD_CU_SIZE_32:
return MALI_AFRC_BLOCK_SIZE_32;
default:
UNREACHABLE("invalid code unit size");
}
}
#endif
#ifdef __cplusplus
} /* extern C */
#endif
#endif
File diff suppressed because it is too large. Load diff
+137
View File
@@ -0,0 +1,137 @@
/*
* Copyright (C) 2018 Alyssa Rosenzweig
* Copyright (C) 2019-2021 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#ifndef __PAN_BLEND_H__
#define __PAN_BLEND_H__
#include "genxml/gen_macros.h"
#include "compiler/nir/nir.h"
#include "util/blend.h"
#include "util/format/u_format.h"
struct MALI_BLEND_EQUATION;
struct pan_blend_equation {
unsigned blend_enable : 1;
unsigned is_float : 1;
enum pipe_blend_func rgb_func : 3;
enum pipe_blendfactor rgb_src_factor : 5;
enum pipe_blendfactor rgb_dst_factor : 5;
enum pipe_blend_func alpha_func : 3;
enum pipe_blendfactor alpha_src_factor : 5;
enum pipe_blendfactor alpha_dst_factor : 5;
unsigned color_mask : 4;
};
struct pan_blend_rt_state {
/* RT format */
enum pipe_format format;
/* Number of samples */
unsigned nr_samples;
struct pan_blend_equation equation;
};
struct pan_blend_state {
bool alpha_to_one;
bool logicop_enable;
enum pipe_logicop logicop_func;
float constants[4];
unsigned rt_count;
struct pan_blend_rt_state rts[8];
};
struct pan_blend_shader_key {
enum pipe_format format;
nir_alu_type src0_type, src1_type;
uint32_t rt : 3;
uint32_t logicop_enable : 1;
uint32_t logicop_func : 4;
uint32_t nr_samples : 5;
uint32_t alpha_to_one : 1;
uint32_t padding : 17;
struct pan_blend_equation equation;
};
bool pan_blend_reads_dest(const struct pan_blend_equation eq);
bool pan_blend_can_fixed_function(unsigned arch,
const struct pan_blend_equation equation,
bool supports_2src);
bool pan_blend_is_opaque(const struct pan_blend_equation eq);
bool pan_blend_alpha_zero_nop(const struct pan_blend_equation eq);
bool pan_blend_alpha_one_store(const struct pan_blend_equation eq);
unsigned pan_blend_constant_mask(const struct pan_blend_equation eq);
void pan_blend_optimize_equation(struct pan_blend_equation *eq,
enum pipe_format format,
const float *constants);
/* Fixed-function blending only supports a single constant, so if multiple bits
* are set in constant_mask, the constants must match. Therefore we may pick
* just the first constant. */
static inline float
pan_blend_get_constant(unsigned mask, const float *constants)
{
return mask ? constants[ffs(mask) - 1] : 0.0;
}
/* v6 doesn't support blend constants in FF blend equations whatsoever, and v7
* only uses the constant from RT 0 (TODO: what if it's the same constant? or a
* constant is shared?) */
static inline bool
pan_blend_supports_constant(unsigned arch, unsigned rt)
{
return !((arch == 6) || (arch == 7 && rt > 0));
}
/* The SOURCE_2 value is new in Bifrost */
static inline bool
pan_blend_supports_2src(unsigned arch)
{
return (arch >= 6);
}
bool pan_blend_is_homogenous_constant(unsigned mask, const float *constants);
uint16_t pan_pack_blend_constant(enum pipe_format format, float cons);
void pan_blend_to_fixed_function_equation(const struct pan_blend_equation eq,
struct MALI_BLEND_EQUATION *equation);
enum mali_register_file_format pan_blend_type_from_nir(nir_alu_type nir_type);
uint32_t pan_pack_blend(const struct pan_blend_equation equation);
#ifdef PAN_ARCH
enum pipe_format GENX(pan_blend_shader_fmt)(enum pipe_format format);
nir_shader *GENX(pan_blend_create_shader)(const struct pan_blend_state *state,
nir_alu_type src0_type,
nir_alu_type src1_type, unsigned rt);
#if PAN_ARCH >= 6
uint64_t GENX(pan_blend_get_internal_desc)(enum pipe_format fmt, unsigned rt,
unsigned force_size, bool dithered);
#if PAN_ARCH < 9
enum mali_register_file_format
GENX(pan_fixup_blend_type)(nir_alu_type T_size, enum pipe_format format);
#endif
#endif
#endif
#endif
+120
View File
@@ -0,0 +1,120 @@
/*
* Copyright (C) 2026 Arm Ltd.
*
* Derived from pan_texture.c which is:
* Copyright (C) 2008 VMware, Inc.
* Copyright (C) 2014 Broadcom
* Copyright (C) 2018-2019 Alyssa Rosenzweig
* Copyright (C) 2019-2020 Collabora, Ltd.
* Copyright (C) 2024 Arm Ltd.
*
* SPDX-License-Identifier: MIT
*/
#include "pan_buffer.h"
#include "pan_desc.h"
#include "pan_format.h"
#include "pan_texture.h"
#include "pan_util.h"
#if PAN_ARCH >= 9
void
GENX(pan_buffer_texture_emit)(const struct pan_buffer_view *bview,
struct mali_buffer_packed *out)
{
unsigned stride = util_format_get_blocksize(bview->format);
struct MALI_INTERNAL_CONVERSION conv = {
.memory_format = GENX(pan_format_from_pipe_format)(bview->format)->hw,
.raw = false,
};
uint64_t buffer_size = bview->width_el * stride;
pan_pack(out, BUFFER, cfg) {
cfg.type = MALI_DESCRIPTOR_TYPE_BUFFER;
cfg.buffer_type = MALI_BUFFER_TYPE_STRUCTURE;
#if PAN_ARCH >= 11
cfg.size = buffer_size & BITFIELD_MASK(32);
cfg.size_hi = buffer_size >> 32;
#else
cfg.size = buffer_size;
#endif
cfg.address = bview->base;
cfg.stride = stride;
cfg.conversion = conv;
}
}
#elif PAN_ARCH >= 6
void
GENX(pan_buffer_texture_emit)(const struct pan_buffer_view *bview,
struct mali_attribute_buffer_packed *out_buf,
struct mali_attribute_packed *out_attrib)
{
unsigned stride = util_format_get_blocksize(bview->format);
uint32_t hw_fmt = GENX(pan_format_from_pipe_format)(bview->format)->hw;
/* The base address has shr(6) applied so we need to use the offset of the
* attribute to get the last 6 bits.
*/
uint64_t base = bview->base & ~0x3f;
uint32_t offset = bview->base & 0x3f;
pan_pack(out_buf, ATTRIBUTE_BUFFER, cfg) {
cfg.type = MALI_ATTRIBUTE_TYPE_1D;
cfg.pointer = base;
cfg.stride = stride;
/* The ATTRIBUTE_BUFFER.size specifies the size of the buffer starting
* at ATTRIBUTE_BUFFER.pointer and and ATTRIBUTE.offset specifies an
* offset into that window. If we're going to use it to offset the base
* of the buffer, we need to increase the size as well.
*/
cfg.size = bview->width_el * stride + offset;
}
pan_pack(out_attrib, ATTRIBUTE, cfg) {
cfg.format = hw_fmt;
cfg.offset = offset;
cfg.offset_enable = offset != 0;
}
}
#else
void
GENX(pan_buffer_texture_emit)(const struct pan_buffer_view *bview,
struct mali_texture_packed *out,
const struct pan_ptr *payload)
{
uint32_t mali_format = GENX(pan_format_from_pipe_format)(bview->format)->hw;
static const unsigned char rgba_swizzle[4] = {
PIPE_SWIZZLE_X,
PIPE_SWIZZLE_Y,
PIPE_SWIZZLE_Z,
PIPE_SWIZZLE_W,
};
pan_cast_and_pack(payload->cpu, SURFACE_WITH_STRIDE, cfg) {
cfg.pointer = bview->base;
cfg.row_stride = 0;
cfg.surface_stride = 0;
}
pan_pack(out, TEXTURE, cfg) {
cfg.dimension = MALI_TEXTURE_DIMENSION_1D;
cfg.format = mali_format;
cfg.width = bview->width_el;
cfg.height = 1;
cfg.sample_count = 1;
cfg.swizzle = pan_translate_swizzle_4(rgba_swizzle);
cfg.texel_ordering = MALI_TEXTURE_LAYOUT_LINEAR;
cfg.levels = 1;
cfg.array_size = 1;
}
}
#endif
+43
View File
@@ -0,0 +1,43 @@
/*
* Copyright (C) 2026 Arm Ltd.
*
* Derived from pan_texture.h which is:
* Copyright (C) 2008 VMware, Inc.
* Copyright (C) 2014 Broadcom
* Copyright (C) 2018-2019 Alyssa Rosenzweig
* Copyright (C) 2019-2020 Collabora, Ltd.
* Copyright (C) 2025 Arm Ltd.
*
* SPDX-License-Identifier: MIT
*/
#ifndef __PAN_BUFFER_H
#define __PAN_BUFFER_H
#ifndef PAN_ARCH
#error "PAN_ARCH must be defined"
#endif
#include "genxml/gen_macros.h"
struct pan_buffer_view;
struct mali_buffer_packed;
struct mali_attribute_buffer_packed;
struct mali_attribute_packed;
struct mali_texture_packed;
struct pan_ptr;
#if PAN_ARCH >= 9
void GENX(pan_buffer_texture_emit)(const struct pan_buffer_view *bview,
struct mali_buffer_packed *out);
#elif PAN_ARCH >= 6
void GENX(pan_buffer_texture_emit)(const struct pan_buffer_view *bview,
struct mali_attribute_buffer_packed *out_buf,
struct mali_attribute_packed *out_attrib);
#else
void GENX(pan_buffer_texture_emit)(const struct pan_buffer_view *bview,
struct mali_texture_packed *out,
const struct pan_ptr *payload);
#endif
#endif
+153
View File
@@ -0,0 +1,153 @@
/*
* Copyright (C) 2019-2021 Collabora, Ltd.
* Copyright (C) 2019 Alyssa Rosenzweig
* SPDX-License-Identifier: MIT
*/
#include "genxml/gen_macros.h"
#include <string.h>
#include "util/format_srgb.h"
#include "util/rounding.h"
#include "util/u_pack_color.h"
#include "pan_format.h"
#include "pan_util.h"
/* Clear colours are packed as the internal format of the tilebuffer, looked up
* in the blendable formats table given the render target format.
*
* Raw formats may emulate arbitrary formats with blend shaders. For these, we
* defer to util_pack_colour to pack in the API format.
*
* Blendable formats, on the other hand, include extra "fractional" bits in the
* tilebuffer for dithering. These have a packed fixed-point representation:
* for a channel with m integer bits and n fractional bits, multiply by ((2^m)
* - 1) * 2^n and round to the nearest even.
*/
/* Replicate a 32-bit value to fill 128-bit */
static void
pan_pack_color_32(uint32_t *packed, uint32_t v)
{
for (unsigned i = 0; i < 4; ++i)
packed[i] = v;
}
/* For m integer bits and n fractional bits, calculate the conversion factor,
* multiply the source value, and convert to integer rounding to even. When
* dithering, the fractional bits are used. When not dithered, only the integer
* bits are used and the fractional bits must remain zero. */
static inline uint32_t
float_to_fixed(float f, unsigned bits_int, unsigned bits_frac, bool dither)
{
uint32_t m = (1 << bits_int) - 1;
if (dither) {
float factor = m << bits_frac;
return _mesa_roundevenf(f * factor);
} else {
uint32_t v = _mesa_roundevenf(f * (float)m);
return v << bits_frac;
}
}
struct mali_tib_layout {
unsigned int_r, frac_r;
unsigned int_g, frac_g;
unsigned int_b, frac_b;
unsigned int_a, frac_a;
};
/* clang-format off */
static const struct mali_tib_layout tib_layouts[] = {
[MALI_COLOR_BUFFER_INTERNAL_FORMAT_R8G8B8A8] = { 8, 0, 8, 0, 8, 0, 8, 0 },
[MALI_COLOR_BUFFER_INTERNAL_FORMAT_R10G10B10A2] = { 10, 0, 10, 0, 10, 0, 2, 0 },
[MALI_COLOR_BUFFER_INTERNAL_FORMAT_R8G8B8A2] = { 8, 2, 8, 2, 8, 2, 2, 0 },
[MALI_COLOR_BUFFER_INTERNAL_FORMAT_R4G4B4A4] = { 4, 4, 4, 4, 4, 4, 4, 4 },
[MALI_COLOR_BUFFER_INTERNAL_FORMAT_R5G6B5A0] = { 5, 5, 6, 4, 5, 5, 0, 2 },
[MALI_COLOR_BUFFER_INTERNAL_FORMAT_R5G5B5A1] = { 5, 5, 5, 5, 5, 5, 1, 1 },
};
/* clang-format on */
/* Raw values are stored as-is but replicated for multisampling */
static void
pan_pack_raw(uint32_t *packed, const union pipe_color_union *color,
enum pipe_format format)
{
union util_color out = {0};
unsigned size = util_format_get_blocksize(format);
assert(size <= 16);
util_pack_color(color->f, format, &out);
if (size == 1) {
unsigned b = out.ui[0] & 0xff;
unsigned w = b | (b << 8);
pan_pack_color_32(packed, w | (w << 16));
} else if (size == 2) {
unsigned w = out.ui[0] & 0xffff;
pan_pack_color_32(packed, w | (w << 16));
} else if (size <= 4) {
pan_pack_color_32(packed, out.ui[0]);
} else if (size <= 8) {
memcpy(packed + 0, out.ui, 8);
memcpy(packed + 2, out.ui, 8);
} else {
memcpy(packed, out.ui, 16);
}
}
void
pan_pack_color(const struct pan_blendable_format *blendable_formats,
uint32_t *packed, const union pipe_color_union *color,
enum pipe_format format, bool dithered)
{
enum mali_color_buffer_internal_format internal =
blendable_formats[format].internal;
if (util_format_is_float(format) ||
internal == MALI_COLOR_BUFFER_INTERNAL_FORMAT_RAW_VALUE) {
pan_pack_raw(packed, color, format);
return;
}
/* Saturate to [0, 1] by definition of UNORM. Prevents overflow. */
float r = SATURATE(color->f[0]);
float g = SATURATE(color->f[1]);
float b = SATURATE(color->f[2]);
float a = SATURATE(color->f[3]);
/* Fill in alpha = 1.0 by default */
if (!util_format_has_alpha(format))
a = 1.0;
/* Convert colourspace while we still have floats */
if (util_format_is_srgb(format)) {
r = util_format_linear_to_srgb_float(r);
g = util_format_linear_to_srgb_float(g);
b = util_format_linear_to_srgb_float(b);
}
/* Look up the layout of the tilebuffer */
assert(internal < ARRAY_SIZE(tib_layouts));
struct mali_tib_layout l = tib_layouts[internal];
unsigned count_r = l.int_r + l.frac_r;
unsigned count_g = l.int_g + l.frac_g + count_r;
unsigned count_b = l.int_b + l.frac_b + count_g;
ASSERTED unsigned count_a = l.int_a + l.frac_a + count_b;
/* Must fill the word */
assert(count_a == 32);
/* Convert the transformed float colour to the given layout */
uint32_t ur = float_to_fixed(r, l.int_r, l.frac_r, dithered) << 0;
uint32_t ug = float_to_fixed(g, l.int_g, l.frac_g, dithered) << count_r;
uint32_t ub = float_to_fixed(b, l.int_b, l.frac_b, dithered) << count_g;
uint32_t ua = float_to_fixed(a, l.int_a, l.frac_a, dithered) << count_b;
pan_pack_color_32(packed, ur | ug | ub | ua);
}
File diff suppressed because it is too large. Load diff
+417
View File
@@ -0,0 +1,417 @@
/*
* Copyright (C) 2021 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#ifndef __PAN_DESC_H
#define __PAN_DESC_H
#include "genxml/gen_macros.h"
#include "kmod/pan_kmod.h"
#include "pan_image.h"
#include "pan_pool.h"
struct pan_buffer_view {
enum pipe_format format;
struct {
unsigned narrow;
unsigned hdr;
} astc;
unsigned width_el;
uint64_t base;
};
struct pan_compute_dim {
uint32_t x, y, z;
};
struct pan_fb_color_attachment {
const struct pan_image_view *view;
bool *crc_valid;
bool clear;
bool preload;
bool discard;
uint32_t clear_value[4];
};
struct pan_fb_zs_attachment {
struct {
const struct pan_image_view *zs, *s;
} view;
struct {
bool z, s;
} clear;
struct {
bool z, s;
} discard;
struct {
bool z, s;
} preload;
struct {
float depth;
uint8_t stencil;
} clear_value;
};
struct pan_tiler_context {
union {
struct {
uint64_t desc;
/* A tiler descriptor can only handle a limited amount of layers.
* If the number of layers is bigger than this, several tiler
* descriptors will be issued, each with a different layer_offset.
*
* Note: on v14+, only up to one tiler descriptor is supported and
* layer_offset is deprecated.
*/
uint8_t layer_offset;
} valhall;
struct {
uint64_t desc;
} bifrost;
struct {
/* Sum of vertex counts (for non-indexed draws), index counts, or ~0 if
* any indirect draws are used. Helps tune hierarchy masks.
*/
uint32_t vertex_count;
bool disable;
bool no_hierarchical_tiling;
uint64_t polygon_list;
struct {
uint64_t start;
unsigned size;
} heap;
} midgard;
};
};
struct pan_tls_info {
struct {
uint64_t ptr;
unsigned size;
} tls;
struct {
unsigned instances;
uint64_t ptr;
unsigned size;
} wls;
};
struct pan_fb_bifrost_info {
struct {
struct pan_ptr dcds;
unsigned modes[3];
} pre_post;
};
struct pan_bbox {
unsigned minx, miny, maxx, maxy;
};
struct pan_fb_info {
unsigned width, height;
/* Draw-extent controlled by viewports/scissors.
* Max values are exclusive */
struct pan_bbox draw_extent;
/* frame_bounding_box controls the bounding box in the framebuffer
* descriptor for the entire pass. This is being controlled by the
* renderArea of a renderpass in Vulkan. On GL, this covers the
* entire frame. Max values are exclusive. */
struct pan_bbox frame_bounding_box;
unsigned nr_samples;
unsigned force_samples; /* samples used for rasterization */
unsigned rt_count;
struct pan_fb_color_attachment rts[8];
struct pan_fb_zs_attachment zs;
struct {
unsigned stride;
uint64_t base;
} tile_map;
union {
struct pan_fb_bifrost_info bifrost;
};
/* Optimal tile buffer size. */
unsigned tile_buf_budget;
unsigned z_tile_buf_budget;
unsigned tile_size;
unsigned cbuf_allocation;
/* Sample position array. */
uint64_t sample_positions;
/* Only used on Valhall */
bool sprite_coord_origin;
bool first_provoking_vertex;
bool allow_hsr_prepass;
/* indicates whether pixel local storage is enabled */
bool pls_enabled;
};
struct pan_clean_tile {
/* clean_tile_write_enable mask on the 8 color attachments. */
uint8_t write_rt_mask;
/* clean_tile_write_enable flag on the depth/stencil attachment. */
uint8_t write_zs : 1;
};
static inline bool
pan_clean_tile_write_rt_enabled(struct pan_clean_tile clean_tile,
unsigned index)
{
return (clean_tile.write_rt_mask >> index) & 1;
}
static inline bool
pan_clean_tile_write_zs_enabled(struct pan_clean_tile clean_tile)
{
return clean_tile.write_zs;
}
static inline bool
pan_clean_tile_write_any_set(struct pan_clean_tile clean_tile)
{
return clean_tile.write_rt_mask || clean_tile.write_zs;
}
static unsigned
pan_zsbuf_bytes_per_pixel(const struct pan_fb_info *fb)
{
unsigned samples = fb->nr_samples;
const struct pan_image_view *zs_view = fb->zs.view.zs;
if (zs_view)
samples = zs_view->nr_samples;
const struct pan_image_view *s_view = fb->zs.view.s;
if (s_view)
samples = MAX2(samples, s_view->nr_samples);
/* Depth is always stored in a 32-bit float. Stencil requires depth to
* be allocated, but doesn't have it's own budget; it's tied to the
* depth buffer.
*/
return sizeof(float) * samples;
}
static inline unsigned
pan_wls_instances(const struct pan_compute_dim *dim)
{
return util_next_power_of_two(dim->x) * util_next_power_of_two(dim->y) *
util_next_power_of_two(dim->z);
}
static inline unsigned
pan_wls_adjust_size(unsigned wls_size)
{
return util_next_power_of_two(MAX2(wls_size, 128));
}
static inline unsigned
pan_calc_workgroups_per_task(const struct pan_compute_dim *shader_local_size,
const struct pan_kmod_dev_props *props)
{
/* Each shader core can run N tasks and a total of M threads at any single
* time, thus each task should ideally have no more than M/N threads. */
unsigned max_threads_per_task =
props->max_threads_per_core / props->max_tasks_per_core;
/* To achieve the best utilization, we should aim for as many workgroups
* per tasks as we can fit without exceeding the above thread limit */
unsigned threads_per_wg =
shader_local_size->x * shader_local_size->y * shader_local_size->z;
assert(threads_per_wg > 0 && threads_per_wg <= props->max_threads_per_wg);
unsigned wg_per_task = DIV_ROUND_UP(max_threads_per_task, threads_per_wg);
assert(wg_per_task > 0 && wg_per_task <= max_threads_per_task);
return wg_per_task;
}
static inline unsigned
pan_calc_wls_instances(const struct pan_compute_dim *shader_local_size,
const struct pan_kmod_dev_props *props,
const struct pan_compute_dim *dim)
{
/* NOTE: If the instance count is lower than the number of workgroups
* being dispatched, the HW will hold back workgroups until instances
* can be reused. */
unsigned instances;
unsigned wg_per_task =
pan_calc_workgroups_per_task(shader_local_size, props);
unsigned max_instances_per_core =
util_next_power_of_two(wg_per_task * props->max_tasks_per_core);
/* Not passing workgroup dimensions implies indirect compute. */
if (!dim) {
/* Assume we utilize all shader cores to the max */
instances = max_instances_per_core;
} else {
/* NOTE: There is no benefit from allocating more instances than what
* can concurrently be used by the HW */
instances = MIN2(pan_wls_instances(dim), max_instances_per_core);
}
return instances;
}
static inline unsigned
pan_calc_total_wls_size(unsigned wls_size, unsigned wls_instances,
unsigned max_core_id_plus_one)
{
unsigned size = pan_wls_adjust_size(wls_size);
return size * wls_instances * max_core_id_plus_one;
}
#ifdef PAN_ARCH
static inline enum mali_sample_pattern
pan_sample_pattern(unsigned samples)
{
switch (samples) {
case 1:
return MALI_SAMPLE_PATTERN_SINGLE_SAMPLED;
#if PAN_ARCH >= 12
case 2:
return MALI_SAMPLE_PATTERN_ROTATED_2X_GRID;
#endif
case 4:
return MALI_SAMPLE_PATTERN_ROTATED_4X_GRID;
case 8:
return MALI_SAMPLE_PATTERN_D3D_8X_GRID;
#if PAN_ARCH >= 5
case 16:
return MALI_SAMPLE_PATTERN_D3D_16X_GRID;
#endif
default:
UNREACHABLE("Unsupported sample count");
}
}
static inline struct pan_image_block_size
pan_effective_tile_block_size(unsigned tile_size)
{
/* Tile is either a square or a rect whose width is twice the height. */
unsigned shift_h = util_logbase2(tile_size);
unsigned shift_w = shift_h + 1;
unsigned h = 1 << (shift_h >> 1);
unsigned w = 1 << (shift_w >> 1);
return (struct pan_image_block_size){w, h};
}
#if PAN_ARCH >= 6
/* All GPUs starting from Bifrost are affected by issue TSIX-2033:
*
* Forcing clean_tile_writes breaks INTERSECT readbacks
*
* To workaround, use the pre-frame shader mode ALWAYS instead of INTERSECT if
* clean_tile_write_enable is set on either one of the color, depth or stencil
* buffers. Since INTERSECT is a hint that the hardware may ignore, this
* cannot affect correctness, only performance. */
static enum mali_pre_post_frame_shader_mode
pan_fix_frame_shader_mode(enum mali_pre_post_frame_shader_mode mode,
bool force_clean_tile)
{
if (force_clean_tile && mode == MALI_PRE_POST_FRAME_SHADER_MODE_INTERSECT)
return MALI_PRE_POST_FRAME_SHADER_MODE_ALWAYS;
else
return mode;
}
#endif
void GENX(pan_select_tile_size)(struct pan_fb_info *fb);
bool GENX(pan_force_clean_write_on)(const struct pan_image *image,
unsigned fb_tile_size_px);
struct pan_clean_tile
GENX(pan_get_clean_tile_info)(const struct pan_fb_info *fb);
void GENX(pan_emit_tls)(const struct pan_tls_info *info,
struct mali_local_storage_packed *out);
int GENX(pan_select_crc_rt)(const struct pan_fb_info *fb, unsigned tile_size);
struct pan_attachment_info {
const struct pan_image_view *iview;
unsigned layer_or_z_slice;
unsigned fb_tile_size_px;
};
#if PAN_ARCH >= 5
void GENX(pan_emit_default_color_attachment)(enum pipe_format format,
void *payload);
void GENX(pan_emit_linear_color_attachment)(const struct pan_attachment_info *att,
void *payload);
void GENX(pan_emit_linear_s_attachment)(const struct pan_attachment_info *att,
void *payload);
void GENX(pan_emit_linear_zs_attachment)(const struct pan_attachment_info *att,
void *payload);
void GENX(pan_emit_u_tiled_color_attachment)(const struct pan_attachment_info *att,
void *payload);
void GENX(pan_emit_u_tiled_s_attachment)(const struct pan_attachment_info *att,
void *payload);
void GENX(pan_emit_u_tiled_zs_attachment)(const struct pan_attachment_info *att,
void *payload);
void GENX(pan_emit_afbc_color_attachment)(const struct pan_attachment_info *att,
void *payload);
void GENX(pan_emit_afbc_zs_attachment)(const struct pan_attachment_info *att,
void *payload);
void GENX(pan_emit_afbc_s_attachment)(const struct pan_attachment_info *att,
void *payload);
#endif
#if PAN_ARCH >= 10
void
GENX(pan_emit_interleaved_64k_color_attachment)(const struct pan_attachment_info *att,
void *payload);
void GENX(pan_emit_interleaved_64k_zs_attachment)(const struct pan_attachment_info *att,
void *payload);
void GENX(pan_emit_interleaved_64k_s_attachment)(const struct pan_attachment_info *att,
void *payload);
void GENX(pan_emit_afrc_color_attachment)(const struct pan_attachment_info *att,
void *payload);
#endif
struct pan_fbd_descs {
#if PAN_ARCH <= 13
struct mali_framebuffer_packed *fbd;
#endif
#if PAN_ARCH >= 5
struct mali_zs_crc_extension_packed *zs_crc;
struct mali_render_target_packed *rts;
#endif
};
unsigned GENX(pan_emit_fbd)(const struct pan_fb_info *fb, unsigned layer_idx,
const struct pan_tls_info *tls,
const struct pan_tiler_context *tiler_ctx,
const struct pan_fbd_descs *out);
#if PAN_ARCH >= 6
unsigned GENX(pan_select_tiler_hierarchy_mask)(uint32_t width, uint32_t height,
uint32_t max_levels,
uint32_t tile_size,
uint32_t mem_budget);
#endif
#if PAN_ARCH <= 9
void GENX(pan_emit_fragment_job_payload)(const struct pan_fb_info *fb,
uint64_t fbd, void *out);
#endif
#endif /* ifdef PAN_ARCH */
#endif
+148
View File
@@ -0,0 +1,148 @@
/*
* Copyright (C) 2022 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#include "genxml/gen_macros.h"
#include "pan_earlyzs.h"
#include "panfrost/compiler/pan_compiler.h"
/*
* Return an "early" mode. If it is known that the depth/stencil tests always
* pass (so the shader is always executed), weak early is usually faster than
* force early.
*/
static enum mali_pixel_kill
best_early_mode(bool zs_always_passes, bool force_early)
{
if (zs_always_passes && !force_early)
return MALI_PIXEL_KILL_WEAK_EARLY;
else
return MALI_PIXEL_KILL_FORCE_EARLY;
}
/*
* Analyze a fragment shader and provided API state to determine the early-ZS
* configuration. The order of arguments must match the order of states in the
* lookup table, synchronized with pan_earlyzs_get.
*/
static struct pan_earlyzs_state
analyze(const struct pan_shader_info *s, bool writes_zs_or_oq,
bool alpha_to_coverage, bool zs_always_passes,
enum pan_earlyzs_zs_tilebuf_read zs_read,
bool should_force_early_update)
{
/* If the shader writes depth or stencil, all depth/stencil tests must
* be deferred until the value is known after the ZS_EMIT instruction,
* if present. ZS_EMIT must precede ATEST, so the value is known when
* ATEST executes, justifying the late test/update.
* Also, if alpha_to_coverage is set that also forces a late update.
* NOTE: it's not at all clear why alpha_to_coverage always requires
* a late update; the late update should only really be required if
* we're writing z or stencil, or testing for occlusion queries.
* The docs are somewhat contradictory on this point.
* But empirically we observe this requirement on Valhall, and doing
* the update later is never wrong (just potentially a bit slower).
*/
bool shader_writes_zs = (s->fs.writes_depth || s->fs.writes_stencil);
bool late_update = shader_writes_zs || alpha_to_coverage;
bool late_kill = shader_writes_zs;
bool force_early_update =
s->fs.early_fragment_tests || should_force_early_update;
bool force_early_kill = s->fs.early_fragment_tests;
/* Late coverage updates are required if the coverage mask depends on
* the results of the shader. Discards are implemented as coverage mask
* updates and must be considered. Strictly, depth/stencil writes may
* also update the coverage mask, but these already force late updates.
*/
bool late_coverage =
s->fs.writes_coverage || s->fs.can_discard || alpha_to_coverage;
/* Late coverage mask updates may affect the value written to the
* depth/stencil buffer (if a pixel is discarded entirely). However,
* they do not affect depth/stencil testing. So they may only matter if
* depth or stencil is written.
*
* That dependency does mean late coverage mask updates require late
* depth/stencil updates.
*
* Similarly, occlusion queries count samples that pass the
* depth/stencil tests, so occlusion queries with late coverage also
* require a late update.
*/
late_update |= (late_coverage && writes_zs_or_oq);
/* Side effects require late depth/stencil tests to ensure the shader
* isn't killed before the side effects execute.
*/
late_kill |= s->writes_global;
/* Shader reads require late depth/stencil tests to ensure the shader
* isn't killed before the side effects execute, unless the HW supports
* read-only ZS optimization, in which case it can be lowered to
* force-early. */
bool optimize_shader_read_only_zs = false;
if (zs_read != PAN_EARLYZS_ZS_TILEBUF_NOT_READ) {
if (!late_update && zs_read == PAN_EARLYZS_ZS_TILEBUF_READ_OPT) {
optimize_shader_read_only_zs = true;
force_early_update |= true;
} else {
late_update |= true;
}
if (!late_kill && zs_read == PAN_EARLYZS_ZS_TILEBUF_READ_OPT) {
optimize_shader_read_only_zs = true;
force_early_kill |= true;
}
}
/* Finally, the shader may override and force early fragment tests */
late_update &= !s->fs.early_fragment_tests;
late_kill &= !s->fs.early_fragment_tests;
/* Collect results */
return (struct pan_earlyzs_state){
.update = late_update
? MALI_PIXEL_KILL_FORCE_LATE
: best_early_mode(zs_always_passes, force_early_update),
.kill = late_kill ? MALI_PIXEL_KILL_FORCE_LATE
: best_early_mode(zs_always_passes, force_early_kill),
.shader_readonly_zs = optimize_shader_read_only_zs,
};
}
/*
* Analyze a fragment shader to determine all possible early-ZS configurations.
* Returns a lookup table of configurations indexed by the API state.
*/
struct pan_earlyzs_lut
pan_earlyzs_analyze(const struct pan_shader_info *s, unsigned arch)
{
/* On v11+, update operation cannot be weak early */
bool should_force_early_update = arch >= 11;
struct pan_earlyzs_lut lut;
for (unsigned v0 = 0; v0 < 2; ++v0) {
for (unsigned v1 = 0; v1 < 2; ++v1) {
for (unsigned v2 = 0; v2 < 2; ++v2) {
for (unsigned v3 = 0; v3 < PAN_EARLYZS_ZS_TILEBUF_MODE_COUNT;
++v3) {
enum pan_earlyzs_zs_tilebuf_read zs_read = v3;
/* Shader read-only ZS optimization only exists on v10. */
if (arch != 10 && v3 == PAN_EARLYZS_ZS_TILEBUF_READ_OPT)
zs_read = PAN_EARLYZS_ZS_TILEBUF_READ_NO_OPT;
lut.states[v0][v1][v2][v3] =
analyze(s, v0, v1, v2, zs_read, should_force_early_update);
}
}
}
}
return lut;
}
+79
View File
@@ -0,0 +1,79 @@
/*
* Copyright (C) 2022 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#ifndef __PAN_EARLYZS_H__
#define __PAN_EARLYZS_H__
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Early-ZS pair. */
struct pan_earlyzs_state {
/* Z/S test and update */
unsigned update : 2;
/* Pixel kill */
unsigned kill : 2;
/* True if the shader read-only ZS optimization should be enabled */
bool shader_readonly_zs : 1;
/* So it fits in a byte */
unsigned padding : 3;
};
/* ZS tilebuf read access */
enum pan_earlyzs_zs_tilebuf_read {
/* The ZS tile buffer is not read */
PAN_EARLYZS_ZS_TILEBUF_NOT_READ = 0,
/* The ZS tile buffer is read but the read-only optimization must be
* disabled
*/
PAN_EARLYZS_ZS_TILEBUF_READ_NO_OPT,
/* The ZS tile buffer is read and the read-only optimization can be
* enabled
*/
PAN_EARLYZS_ZS_TILEBUF_READ_OPT,
/* Number of ZS read modes */
PAN_EARLYZS_ZS_TILEBUF_MODE_COUNT,
};
/* Internal lookup table. Users should treat as an opaque structure and only
* access through pan_earlyzs_get and pan_earlyzs_analyze. See pan_earlyzs_get
* for definition of the arrays.
*/
struct pan_earlyzs_lut {
struct pan_earlyzs_state states[2][2][2][PAN_EARLYZS_ZS_TILEBUF_MODE_COUNT];
};
/*
* Look up early-ZS state. This is in the draw hot path on Valhall, so this is
* defined inline in the header.
*/
static inline struct pan_earlyzs_state
pan_earlyzs_get(struct pan_earlyzs_lut lut, bool writes_zs_or_oq,
bool alpha_to_coverage, bool zs_always_passes,
enum pan_earlyzs_zs_tilebuf_read zs_read)
{
return lut.states[writes_zs_or_oq][alpha_to_coverage][zs_always_passes]
[zs_read];
}
struct pan_shader_info;
struct pan_earlyzs_lut pan_earlyzs_analyze(const struct pan_shader_info *s,
unsigned arch);
#ifdef __cplusplus
} /* extern C */
#endif
#endif
+283
View File
@@ -0,0 +1,283 @@
/*
* Copyright (C) 2019 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#ifndef __PAN_ENCODER_H
#define __PAN_ENCODER_H
#ifndef __OPENCL_VERSION__
#include "util/macros.h"
#include <stdbool.h>
#include "util/format/u_format.h"
#include "pan_pool.h"
#else
#include "compiler/libcl/libcl.h"
#endif
#include "genxml/gen_macros.h"
/* Tiler structure size computation */
unsigned pan_tiler_header_size(unsigned width, unsigned height, unsigned mask,
bool hierarchy);
unsigned pan_tiler_full_size(unsigned width, unsigned height, unsigned mask,
bool hierarchy);
unsigned pan_choose_hierarchy_mask(unsigned width, unsigned height,
unsigned vertex_count, bool hierarchy);
#if defined(PAN_ARCH) && PAN_ARCH <= 5
static inline unsigned
pan_tiler_get_polygon_list_size(unsigned fb_width, unsigned fb_height,
unsigned vertex_count, bool hierarchy)
{
if (!vertex_count)
return MALI_MIDGARD_TILER_MINIMUM_HEADER_SIZE + 4;
unsigned hierarchy_mask =
pan_choose_hierarchy_mask(fb_width, fb_height, vertex_count, hierarchy);
return pan_tiler_full_size(fb_width, fb_height, hierarchy_mask, hierarchy) +
pan_tiler_header_size(fb_width, fb_height, hierarchy_mask, hierarchy);
}
#endif
/* Stack sizes */
unsigned pan_get_stack_shift(unsigned stack_size);
unsigned pan_get_total_stack_size(unsigned thread_size,
unsigned threads_per_core,
unsigned core_id_range);
/* Attributes / instancing */
static inline unsigned
pan_padded_vertex_count(unsigned vertex_count)
{
if (vertex_count < 10)
return vertex_count;
if (vertex_count < 20)
return (vertex_count + 1) & ~1;
/* First, we have to find the highest set one */
unsigned highest = 32 - __builtin_clz(vertex_count);
/* Using that, we mask out the highest 4-bits */
unsigned n = highest - 4;
unsigned nibble = (vertex_count >> n) & 0xF;
/* Great, we have the nibble. Now we can just try possibilities. Note
* that we don't care about the bottom most bit in most cases, and we
* know the top bit must be 1 */
unsigned middle_two = (nibble >> 1) & 0x3;
switch (middle_two) {
case 0b00:
if (!(nibble & 1))
return (1 << n) * 9;
else
return (1 << (n + 1)) * 5;
case 0b01:
return (1 << (n + 2)) * 3;
case 0b10:
return (1 << (n + 1)) * 7;
case 0b11:
return (1 << (n + 4));
default:
return 0; /* unreachable */
}
}
static inline unsigned
pan_compute_npot_divisor(unsigned hw_divisor, unsigned *divisor_r,
unsigned *divisor_e)
{
unsigned r = util_logbase2(hw_divisor);
uint64_t shift_hi = 32 + r;
uint64_t t = (uint64_t)1 << shift_hi;
uint64_t f0 = t + hw_divisor / 2;
uint64_t fi = f0 / hw_divisor;
uint64_t ff = f0 - fi * hw_divisor;
uint64_t d = fi - (1ul << 31);
*divisor_r = r;
*divisor_e = ff > hw_divisor / 2 ? 1 : 0;
return d;
}
#ifdef PAN_ARCH
/* Records for gl_VertexID and gl_InstanceID use special encodings on Midgard */
#if PAN_ARCH <= 5
static inline void
pan_vertex_id(unsigned padded_count,
struct mali_attribute_vertex_id_packed *attr, bool instanced)
{
pan_pack(attr, ATTRIBUTE_VERTEX_ID, cfg) {
if (instanced) {
cfg.divisor_r = __builtin_ctz(padded_count);
cfg.divisor_p = padded_count >> (cfg.divisor_r + 1);
} else {
/* Large values so the modulo is a no-op */
cfg.divisor_r = 0x1F;
cfg.divisor_p = 0x4;
}
}
}
static inline void
pan_instance_id(unsigned padded_count,
struct mali_attribute_instance_id_packed *attr, bool instanced)
{
pan_pack(attr, ATTRIBUTE_INSTANCE_ID, cfg) {
if (!instanced || padded_count <= 1) {
/* Divide by large number to force to 0 */
cfg.divisor_p = ((1u << 31) - 1);
cfg.divisor_r = 0x1F;
cfg.divisor_e = 0x1;
} else if (util_is_power_of_two_or_zero(padded_count)) {
/* Can't underflow since padded_count >= 2 */
cfg.divisor_r = __builtin_ctz(padded_count) - 1;
} else {
cfg.divisor_p = pan_compute_npot_divisor(padded_count, &cfg.divisor_r,
&cfg.divisor_e);
}
}
}
#endif /* PAN_ARCH <= 5 */
/* Sampler comparison functions are flipped in OpenGL from the hardware, so we
* need to be able to flip accordingly */
static inline enum mali_func
pan_flip_compare_func(enum mali_func f)
{
switch (f) {
case MALI_FUNC_LESS:
return MALI_FUNC_GREATER;
case MALI_FUNC_GREATER:
return MALI_FUNC_LESS;
case MALI_FUNC_LEQUAL:
return MALI_FUNC_GEQUAL;
case MALI_FUNC_GEQUAL:
return MALI_FUNC_LEQUAL;
default:
return f;
}
}
#if PAN_ARCH < 9
/* Compute shaders are invoked with a gl_NumWorkGroups X/Y/Z triplet. Vertex
* shaders are invoked as (1, vertex_count, instance_count). Compute shaders
* also have a gl_WorkGroupSize X/Y/Z triplet. These 6 values are packed
* together in a dynamic bitfield, packed by this routine. */
static inline void
pan_pack_work_groups_compute(struct mali_invocation_packed *out, unsigned num_x,
unsigned num_y, unsigned num_z, unsigned size_x,
unsigned size_y, unsigned size_z,
bool quirk_graphics, bool indirect_dispatch)
{
/* The values needing packing, in order, and the corresponding shifts.
* Indices into shift are off-by-one to make the logic easier */
unsigned values[6] = {size_x, size_y, size_z, num_x, num_y, num_z};
unsigned shifts[7] = {0};
uint32_t packed = 0;
for (unsigned i = 0; i < 6; ++i) {
/* Must be positive, otherwise we underflow */
assert(values[i] >= 1);
/* OR it in, shifting as required */
packed |= ((values[i] - 1) << shifts[i]);
/* How many bits did we use? */
unsigned bit_count = util_logbase2_ceil(values[i]);
/* Set the next shift accordingly */
shifts[i + 1] = shifts[i] + bit_count;
}
pan_pack(out, INVOCATION, cfg) {
cfg.invocations = packed;
cfg.size_y_shift = shifts[1];
cfg.size_z_shift = shifts[2];
cfg.workgroups_x_shift = shifts[3];
if (!indirect_dispatch) {
/* Leave zero for the dispatch shader */
cfg.workgroups_y_shift = shifts[4];
cfg.workgroups_z_shift = shifts[5];
}
/* Quirk: for non-instanced graphics, the blob sets
* workgroups_z_shift = 32. This doesn't appear to matter to
* the hardware, but it's good to be bit-identical. */
if (quirk_graphics && (num_z <= 1))
cfg.workgroups_z_shift = 32;
/* For graphics, set to the minimum efficient value. For
* compute, must equal the workgroup X shift for barriers to
* function correctly */
cfg.thread_group_split =
quirk_graphics ? MALI_SPLIT_MIN_EFFICIENT : cfg.workgroups_x_shift;
}
}
#endif
#ifndef __OPENCL_VERSION__
#if PAN_ARCH >= 5
/* Format conversion */
static inline enum mali_z_internal_format
pan_get_z_internal_format(enum pipe_format fmt)
{
switch (fmt) {
case PIPE_FORMAT_Z16_UNORM:
case PIPE_FORMAT_Z16_UNORM_S8_UINT:
return MALI_Z_INTERNAL_FORMAT_D16;
case PIPE_FORMAT_Z24_UNORM_S8_UINT:
case PIPE_FORMAT_Z24X8_UNORM:
case PIPE_FORMAT_Z24_UNORM_PACKED:
return MALI_Z_INTERNAL_FORMAT_D24;
case PIPE_FORMAT_Z32_FLOAT:
case PIPE_FORMAT_Z32_FLOAT_S8X24_UINT:
return MALI_Z_INTERNAL_FORMAT_D32;
default:
UNREACHABLE("Unsupported depth/stencil format.");
}
}
#endif
#endif
#endif /* PAN_ARCH */
#ifndef __OPENCL_VERSION__
#if PAN_ARCH >= 9
static inline void
pan_make_resource_table(struct pan_ptr base, unsigned index, uint64_t address,
unsigned resource_count)
{
if (resource_count == 0)
return;
struct mali_resource_packed *res = base.cpu;
pan_pack(&res[index], RESOURCE, cfg) {
cfg.address = address;
cfg.size = resource_count * pan_size(BUFFER);
}
}
#endif
#endif
#endif
+800
View File
@@ -0,0 +1,800 @@
/*
* Copyright (C) 2026 Collabora, Ltd.
* Copyright (C) 2026 Arm Ltd.
* SPDX-License-Identifier: MIT
*/
#include "pan_fb.h"
#include "pan_afbc.h"
#include "pan_afrc.h"
#include "pan_desc.h"
#include "pan_encoder.h"
#include "pan_format.h"
#include "pan_image.h"
#include "pan_props.h"
#include "pan_util.h"
static unsigned
pan_bytes_per_pixel_tib(enum pipe_format format)
{
const struct pan_blendable_format *bf =
GENX(pan_blendable_format_from_pipe_format)(format);
return pan_format_tib_size(format, bf->internal);
}
void
GENX(pan_select_fb_tile_size)(struct pan_fb_layout *fb)
{
uint32_t rt_B_per_sa = 0;
for (unsigned rt = 0; rt < fb->rt_count; rt++) {
enum pipe_format format = fb->rt_formats[rt];
if (format == PIPE_FORMAT_NONE)
continue;
const struct pan_blendable_format *bf =
GENX(pan_blendable_format_from_pipe_format)(format);
rt_B_per_sa += pan_format_tib_size(format, bf->internal);
}
/* The PLS area overlaps with the color targets */
rt_B_per_sa = MAX2(rt_B_per_sa, fb->pls_size_B);
const uint32_t rt_B_per_px = rt_B_per_sa * fb->sample_count;
/* We always have depth and it's is always stored in a 32-bit float.
* Stencil requires depth to be allocated, but doesn't have it's own
* budget; it's tied to the depth buffer.
*/
const uint32_t z_B_per_px = sizeof(float) * fb->sample_count;
fb->tile_size_px =
MIN2(fb->tile_rt_budget_B >> util_logbase2_ceil(rt_B_per_px),
fb->tile_z_budget_B >> util_logbase2_ceil(z_B_per_px));
/* Check if we're using too much tile-memory; if we are, try disabling
* pipelining. This works because we're starting with an optimistic half
* of the tile-budget, so we actually have another half that can be used.
*
* On v6 GPUs, doing this is not allowed; they *have* to pipeline.
*/
if (PAN_ARCH != 6 && fb->tile_size_px < 4 * 4)
fb->tile_size_px *= 2;
/* Clamp tile size to hardware limits */
fb->tile_size_px =
MIN2(fb->tile_size_px, pan_max_effective_tile_size(PAN_ARCH));
assert(util_is_power_of_two_nonzero(fb->tile_size_px));
assert(fb->tile_size_px >= 4 * 4);
/* Colour buffer allocations must be 1K aligned. */
fb->tile_rt_alloc_B = ALIGN_POT(rt_B_per_px * fb->tile_size_px, 1024);
#if PAN_ARCH == 6
assert(fb->tile_rt_alloc_B <= fb->tile_rt_budget_B && "tile too big");
#else
assert(fb->tile_rt_alloc_B <= fb->tile_rt_budget_B * 2 && "tile too big");
#endif
}
static void
align_fb_tiling_area_for_image_plane(struct pan_fb_layout *fb,
struct pan_image_plane_ref pref)
{
if (!pref.image)
return;
struct pan_image_block_size block_size;
if (drm_is_afbc(pref.image->props.modifier)) {
/* For AFBC render targets, the hardware always writes full superblocks.
* In order to ensure we don't write garbage, we need to expand the
* render area accordingly and load the border pixels.
*/
block_size = pan_afbc_renderblock_size(pref.image->props.modifier);
assert(block_size.width >= 16 && block_size.height >= 16);
} else if (drm_is_afrc(pref.image->props.modifier)) {
/* For AFRC render targets, the hardware always writes full clumps. In
* order to ensure we don't write garbage, we need to expand the render
* area accordingly and load the border pixels.
*/
bool scan = pan_afrc_is_scan(pref.image->props.modifier);
block_size = pan_afrc_clump_size(pref.image->props.format, scan);
} else {
/* No alignment requirements */
return;
}
fb->tiling_area_px = pan_fb_bbox_align(fb->tiling_area_px,
block_size.width,
block_size.height);
}
void
GENX(pan_align_fb_tiling_area)(struct pan_fb_layout *fb,
const struct pan_fb_store *store)
{
if (store == NULL)
return;
for (unsigned rt = 0; rt < fb->rt_count; rt++) {
if (store->rts[rt].store) {
align_fb_tiling_area_for_image_plane(fb,
pan_image_view_get_color_plane(store->rts[rt].iview));
}
}
if (store->zs.store) {
align_fb_tiling_area_for_image_plane(fb,
pan_image_view_get_zs_plane(store->zs.iview));
}
if (store->s.store) {
align_fb_tiling_area_for_image_plane(fb,
pan_image_view_get_s_plane(store->s.iview));
}
}
static void
fold_sample_0_image_load(struct pan_fb_resolve_op_msaa *rm,
const struct pan_image_view *iview,
uint8_t fb_sample_count)
{
if (rm->resolve != PAN_FB_RESOLVE_IMAGE)
return;
if (rm->msaa != PAN_FB_MSAA_COPY_ALL)
return;
assert(pan_image_view_get_nr_samples(iview) == fb_sample_count);
/* If we're loading all samples of the image but only storing SAMPLE_0,
* then we don't actually need all samples in the image. Drop it to a
* SAMPLE_0 load to save bandwidth.
*/
rm->msaa = PAN_FB_MSAA_COPY_SAMPLE_0;
}
static bool
resolve_msaa_can_fold(struct pan_fb_resolve_op_msaa rm,
enum pan_fb_resolve_op self_op,
enum pipe_format format)
{
if (rm.resolve == PAN_FB_RESOLVE_IMAGE) {
/* We already folded away COPY_ALL and all the other MSAA modes produce
* a single per-pixel result across all the samples, i.e. COPY_IDENTICAL.
* We can fold that with any resolve mode.
*/
assert(rm.msaa != PAN_FB_MSAA_COPY_ALL);
return true;
}
if (rm.resolve != self_op)
return false;
/* PAN_FB_MSAA_COPY_IDENTICAL is always foldable */
if (rm.msaa == PAN_FB_MSAA_COPY_IDENTICAL)
return true;
if (self_op == PAN_FB_RESOLVE_Z || self_op == PAN_FB_RESOLVE_S) {
/* Z/S only supports SAMPLE_0 */
return rm.msaa == PAN_FB_MSAA_COPY_SAMPLE_0;
} else {
if (rm.msaa == PAN_FB_MSAA_COPY_AVERAGE &&
!GENX(pan_format_supports_msaa_average)(format))
return false;
/* The color hardware supports SAMPLE_0 and AVERAGE */
return rm.msaa == PAN_FB_MSAA_COPY_SAMPLE_0 ||
rm.msaa == PAN_FB_MSAA_COPY_AVERAGE;
}
}
static void
fold_resolve_into_store_target(enum pipe_format format,
uint8_t fb_sample_count,
enum pan_fb_resolve_op self_op,
struct pan_fb_resolve_target *resolve,
struct pan_fb_store_target *store)
{
if (format == PIPE_FORMAT_NONE)
return;
/* We need SAMPLE_0 if we're going to fold in the resolve */
if (store->msaa != PAN_FB_MSAA_COPY_SAMPLE_0)
return;
fold_sample_0_image_load(&resolve->in_bounds, resolve->iview,
fb_sample_count);
fold_sample_0_image_load(&resolve->in_bounds, resolve->iview,
fb_sample_count);
if (!resolve_msaa_can_fold(resolve->in_bounds, self_op, format) ||
!resolve_msaa_can_fold(resolve->border, self_op, format))
return;
enum pan_fb_msaa_copy_op msaa;
if (resolve->in_bounds.resolve == PAN_FB_RESOLVE_IMAGE) {
/* If they're both IMAGE, there's nothing to fold */
if (resolve->border.resolve == PAN_FB_RESOLVE_IMAGE)
return;
msaa = resolve->border.msaa;
} else if (resolve->border.resolve == PAN_FB_RESOLVE_IMAGE) {
msaa = resolve->in_bounds.msaa;
} else {
/* If neither is an image resolve, the MSAA copy op has to match or one
* of them has to be PAN_FB_MSAA_COPY_IDENTICAL.
*/
if (resolve->in_bounds.msaa != PAN_FB_MSAA_COPY_IDENTICAL) {
if (resolve->border.msaa != PAN_FB_MSAA_COPY_IDENTICAL &&
resolve->border.msaa != resolve->in_bounds.msaa)
return;
msaa = resolve->in_bounds.msaa;
} else {
msaa = resolve->border.msaa;
}
}
/* PAN_FB_MSAA_COPY_IDENTICAL isn't real. Use SAMPLE_0 */
if (msaa == PAN_FB_MSAA_COPY_IDENTICAL)
msaa = PAN_FB_MSAA_COPY_SAMPLE_0;
/* At this point, we know we can fold the resolve */
store->msaa = msaa;
if (resolve->in_bounds.resolve != PAN_FB_RESOLVE_IMAGE) {
resolve->in_bounds.resolve = PAN_FB_RESOLVE_NONE;
resolve->in_bounds.msaa = PAN_FB_MSAA_COPY_ALL;
}
if (resolve->border.resolve != PAN_FB_RESOLVE_IMAGE) {
resolve->border.resolve = PAN_FB_RESOLVE_NONE;
resolve->border.msaa = PAN_FB_MSAA_COPY_ALL;
}
}
void
GENX(pan_fb_fold_resolve_into_store)(const struct pan_fb_layout *fb,
struct pan_fb_resolve *resolve,
struct pan_fb_store *store)
{
if (fb->sample_count == 1)
return;
fold_resolve_into_store_target(fb->z_format, fb->sample_count,
PAN_FB_RESOLVE_Z, &resolve->z, &store->zs);
fold_resolve_into_store_target(fb->s_format, fb->sample_count,
PAN_FB_RESOLVE_S, &resolve->s, &store->s);
for (unsigned rt = 0; rt < fb->rt_count; rt++) {
fold_resolve_into_store_target(fb->rt_formats[rt], fb->sample_count,
PAN_FB_RESOLVE_RT(rt),
&resolve->rts[rt], &store->rts[rt]);
}
}
void
GENX(pan_fill_fb_info)(const struct pan_fb_desc_info *info,
struct pan_fb_info *fbinfo)
{
struct pan_bbox bbox = { 0, 0, 0, 0 };
if (info->fb->width_px > 0 && info->fb->width_px > 0) {
const struct pan_fb_bbox fb_area_px =
pan_fb_bbox_from_xywh(0, 0, info->fb->width_px, info->fb->height_px);
assert(pan_fb_bbox_is_valid(info->fb->tiling_area_px));
const struct pan_fb_bbox bbox_px =
pan_fb_bbox_clamp(info->fb->tiling_area_px, fb_area_px);
bbox = (struct pan_bbox) {
.minx = bbox_px.min_x,
.miny = bbox_px.min_y,
.maxx = bbox_px.max_x,
.maxy = bbox_px.max_y,
};
}
*fbinfo = (struct pan_fb_info) {
.width = info->fb->width_px,
.height = info->fb->height_px,
.draw_extent = bbox,
.frame_bounding_box = bbox,
.nr_samples = info->fb->sample_count,
.rt_count = info->fb->rt_count,
.pls_enabled = info->fb->pls_size_B > 0,
.bifrost.pre_post = {
.dcds.gpu = info->frame_shaders.dcd_pointer,
.modes = {
info->frame_shaders.modes[0],
info->frame_shaders.modes[1],
info->frame_shaders.modes[2],
},
},
.tile_buf_budget = info->fb->tile_rt_budget_B,
.z_tile_buf_budget = info->fb->tile_z_budget_B,
.tile_size = info->fb->tile_size_px,
.cbuf_allocation = info->fb->tile_rt_alloc_B,
.sample_positions = info->sample_pos_array_pointer,
.sprite_coord_origin = info->sprite_coord_origin_max_y,
.first_provoking_vertex = info->provoking_vertex_first,
.allow_hsr_prepass = info->provoking_vertex_first,
};
/* There are cases where we only want to fill out a partial fb_info */
if (info->load == NULL && info->store == NULL)
return;
for (unsigned rt = 0; rt < info->fb->rt_count; rt++) {
fbinfo->rts[rt] = (struct pan_fb_color_attachment) {
.view = info->store->rts[rt].iview,
.clear = info->load->rts[rt].border_load == PAN_FB_LOAD_CLEAR ||
info->load->rts[rt].in_bounds_load == PAN_FB_LOAD_CLEAR,
.preload = info->load->rts[rt].in_bounds_load == PAN_FB_LOAD_IMAGE,
.discard = !info->store->rts[rt].store,
};
if (fbinfo->rts[rt].clear) {
pan_pack_color(GENX(pan_blendable_formats),
fbinfo->rts[rt].clear_value,
&info->load->rts[rt].clear.color,
info->fb->rt_formats[rt],
false /* dithered */);
}
}
if (info->fb->z_format != PIPE_FORMAT_NONE) {
fbinfo->zs.view.zs = info->store->zs.iview;
fbinfo->zs.clear.z = info->load->z.border_load == PAN_FB_LOAD_CLEAR ||
info->load->z.in_bounds_load == PAN_FB_LOAD_CLEAR;
fbinfo->zs.preload.z = info->load->z.in_bounds_load == PAN_FB_LOAD_IMAGE;
fbinfo->zs.discard.z = !info->store->zs.store;
if (fbinfo->zs.clear.z)
fbinfo->zs.clear_value.depth = info->load->z.clear.depth;
}
if (info->fb->s_format != PIPE_FORMAT_NONE) {
fbinfo->zs.view.s = info->store->s.iview;
fbinfo->zs.clear.s = info->load->s.border_load == PAN_FB_LOAD_CLEAR ||
info->load->s.in_bounds_load == PAN_FB_LOAD_CLEAR;
fbinfo->zs.preload.s = info->load->s.in_bounds_load == PAN_FB_LOAD_IMAGE;
fbinfo->zs.discard.s = !info->store->s.store;
if (fbinfo->zs.clear.s)
fbinfo->zs.clear_value.stencil = info->load->s.clear.stencil;
}
}
#if PAN_ARCH >= 5
static enum mali_msaa
translate_msaa_copy_op(const struct pan_fb_layout *fb,
const struct pan_image_view *iview,
enum pan_fb_msaa_copy_op msaa)
{
switch (msaa) {
case PAN_FB_MSAA_COPY_ALL:
assert(pan_image_view_get_nr_samples(iview) == fb->sample_count);
if (fb->sample_count > 1)
return MALI_MSAA_LAYERED;
else
return MALI_MSAA_SINGLE;
case PAN_FB_MSAA_COPY_SINGLE:
case PAN_FB_MSAA_COPY_SAMPLE_0:
return MALI_MSAA_SINGLE;
case PAN_FB_MSAA_COPY_AVERAGE:
/* It must be a blendable format or the hardware can't average */
assert(GENX(pan_blendable_format_from_pipe_format)(iview->format)->internal);
if (fb->sample_count > 1)
return MALI_MSAA_AVERAGE;
else
return MALI_MSAA_SINGLE;
default:
UNREACHABLE("Unsupported MSAA copy op");
}
}
static bool
pan_fb_load_target_always(const struct pan_fb_load_target *target)
{
if (target->in_bounds_load == PAN_FB_LOAD_NONE &&
target->border_load == PAN_FB_LOAD_NONE)
return false;
return target->always;
}
static bool
pan_fb_store_target_always(const struct pan_fb_store_target *target)
{
return target->store && target->always;
}
struct pan_fb_clean_tile
GENX(pan_fb_get_clean_tile)(const struct pan_fb_desc_info *info)
{
const struct pan_fb_layout *fb = info->fb;
const struct pan_fb_load *load = info->load;
const struct pan_fb_store *store = info->store;
struct pan_fb_clean_tile ct = { };
for (unsigned rt = 0; rt < fb->rt_count; rt++) {
if (fb->rt_formats[rt] == PIPE_FORMAT_NONE)
continue;
if ((load && pan_fb_load_target_always(&load->rts[rt])) ||
(store && pan_fb_store_target_always(&store->rts[rt])))
ct.rts |= BITFIELD_BIT(rt);
if (store && store->rts[rt].store) {
const struct pan_image *img =
pan_image_view_get_color_plane(store->rts[rt].iview).image;
if (GENX(pan_force_clean_write_on)(img, fb->tile_size_px))
ct.rts |= BITFIELD_BIT(rt);
}
}
const bool z_always_load = load && pan_fb_load_target_always(&load->z);
const bool s_always_load = load && pan_fb_load_target_always(&load->s);
const bool zs_always_store = store && pan_fb_store_target_always(&store->zs);
const bool s_always_store = store && pan_fb_store_target_always(&store->s);
if (fb->z_format != PIPE_FORMAT_NONE) {
ct.zs = z_always_load || zs_always_store;
if (store && store->zs.store) {
const struct pan_image *img =
pan_image_view_get_zs_plane(store->zs.iview).image;
assert(util_format_get_depth_bits(img->props.format) ==
util_format_get_depth_bits(fb->z_format));
assert(util_format_get_depth_bits(store->zs.iview->format) ==
util_format_get_depth_bits(fb->z_format));
const struct util_format_description *zs_fmt_desc =
util_format_description(img->props.format);
/* If ZS writes stencil, we have to also include stencil loads */
if (util_format_has_stencil(zs_fmt_desc) && s_always_load)
ct.zs = true;
if (GENX(pan_force_clean_write_on)(img, fb->tile_size_px))
ct.zs = true;
}
}
if (fb->s_format != PIPE_FORMAT_NONE) {
ct.s = s_always_load || s_always_store;
if (store && store->s.store) {
const struct pan_image *img =
pan_image_view_get_s_plane(store->s.iview).image;
if (GENX(pan_force_clean_write_on)(img, fb->tile_size_px))
ct.s = true;
}
}
return ct;
}
static void
emit_zs_crc_desc(const struct pan_fb_desc_info *info,
const struct pan_fb_clean_tile ct,
struct mali_zs_crc_extension_packed *zs_crc)
{
const struct pan_fb_layout *fb = info->fb;
const struct pan_fb_store *store = info->store;
pan_pack(zs_crc, ZS_CRC_EXTENSION, cfg) {
if (store && store->zs.store) {
cfg.zs.msaa = translate_msaa_copy_op(fb, store->zs.iview,
store->zs.msaa);
#if PAN_ARCH >= 6
cfg.zs.clean_tile_write_enable = ct.zs;
#else
cfg.zs.clean_pixel_write_enable = ct.zs;
#endif
}
if (store && store->s.store) {
cfg.s.msaa = translate_msaa_copy_op(fb, store->s.iview,
store->s.msaa);
#if PAN_ARCH >= 6
cfg.s.clean_tile_write_enable = ct.s;
#else
cfg.s.clean_pixel_write_enable = ct.s;
#endif
}
/* TODO CRC */
}
if (store && store->zs.store) {
const struct pan_image_view *iview = store->zs.iview;
const struct pan_mod_handler *mod_handler =
pan_image_view_get_zs_plane(iview).image->mod_handler;
assert(info->layer < pan_image_view_get_layer_count(iview));
const struct pan_attachment_info att = {
.iview = iview,
.layer_or_z_slice = iview->first_layer + info->layer,
.fb_tile_size_px = fb->tile_size_px,
};
struct mali_zs_crc_extension_packed zs_part;
mod_handler->emit_zs_attachment(&att, &zs_part);
pan_merge(zs_crc, &zs_part, ZS_CRC_EXTENSION);
}
if (store && store->s.store) {
const struct pan_image_view *iview = store->s.iview;
const struct pan_mod_handler *mod_handler =
pan_image_view_get_s_plane(iview).image->mod_handler;
assert(info->layer < pan_image_view_get_layer_count(iview));
const struct pan_attachment_info att = {
.iview = iview,
.layer_or_z_slice = iview->first_layer + info->layer,
.fb_tile_size_px = fb->tile_size_px,
};
struct mali_zs_crc_extension_packed s_part;
mod_handler->emit_s_attachment(&att, &s_part);
pan_merge(zs_crc, &s_part, ZS_CRC_EXTENSION);
}
/* TODO: CRC */
}
static void
emit_rgb_rt_desc(const struct pan_fb_desc_info *info,
const struct pan_fb_clean_tile ct,
unsigned rt, uint32_t tile_rt_offset_B,
struct mali_rgb_render_target_packed *rgb_rt)
{
const struct pan_fb_layout *fb = info->fb;
const struct pan_fb_load *load = info->load;
const struct pan_fb_store *store = info->store;
if (fb->rt_formats[rt] == PIPE_FORMAT_NONE) {
pan_pack(rgb_rt, RGB_RENDER_TARGET, cfg) {
/* Place non-existent targets at the end. Hardware bounds
* checking should just discard writes and zero reads.
*/
cfg.internal_buffer_offset = fb->tile_rt_alloc_B;
cfg.internal_format = MALI_COLOR_BUFFER_INTERNAL_FORMAT_R8G8B8A8;
cfg.write_enable = false;
#if PAN_ARCH >= 7
cfg.writeback_block_format = MALI_BLOCK_FORMAT_TILED_U_INTERLEAVED;
#endif
}
return;
}
pan_pack(rgb_rt, RGB_RENDER_TARGET, cfg) {
cfg.internal_buffer_offset = tile_rt_offset_B;
cfg.dithering_enable = true;
if (store && store->rts[rt].store) {
cfg.writeback_msaa =
translate_msaa_copy_op(fb, store->rts[rt].iview,
store->rts[rt].msaa);
}
#if PAN_ARCH >= 6
cfg.clean_tile_write_enable = !!(ct.rts & BITFIELD_BIT(rt));
#else
cfg.clean_pixel_write_enable = !!(ct.rts & BITFIELD_BIT(rt));
#endif
if (load && pan_target_has_clear(&load->rts[rt])) {
uint32_t packed[4] = {};
pan_pack_color(GENX(pan_blendable_formats), packed,
&load->rts[rt].clear.color, fb->rt_formats[rt],
false /* dithered */);
cfg.clear = (struct MALI_RT_CLEAR) {
.color_0 = packed[0],
.color_1 = packed[1],
.color_2 = packed[2],
.color_3 = packed[3],
};
}
}
struct mali_render_target_packed desc;
if (store && store->rts[rt].store) {
const struct pan_image_view *iview = store->rts[rt].iview;
const struct pan_mod_handler *mod_handler =
pan_image_view_get_color_plane(iview).image->mod_handler;
assert(info->layer < pan_image_view_get_layer_count(iview));
const struct pan_attachment_info att = {
.iview = iview,
.layer_or_z_slice = iview->first_layer + info->layer,
.fb_tile_size_px = fb->tile_size_px,
};
mod_handler->emit_color_attachment(&att, &desc);
} else {
GENX(pan_emit_default_color_attachment)(fb->rt_formats[rt], &desc);
}
pan_merge(rgb_rt, &desc, RGB_RENDER_TARGET);
}
static void
emit_rts(const struct pan_fb_desc_info *info,
struct mali_rgb_render_target_packed *rts)
{
const struct pan_fb_layout *fb = info->fb;
const struct pan_fb_clean_tile ct = GENX(pan_fb_get_clean_tile)(info);
uint32_t tile_rt_offset_B = 0;
for (unsigned rt = 0; rt < fb->rt_count; rt++) {
emit_rgb_rt_desc(info, ct, rt, tile_rt_offset_B, rts);
rts++;
if (fb->rt_formats[rt] != PIPE_FORMAT_NONE) {
tile_rt_offset_B += pan_bytes_per_pixel_tib(fb->rt_formats[rt]) *
fb->tile_size_px * fb->sample_count;
}
}
assert(tile_rt_offset_B <= fb->tile_rt_alloc_B);
}
#if PAN_ARCH >= 14
uint32_t
GENX(pan_emit_fb_desc)(const struct pan_fb_desc_info *info,
const struct pan_fb_descs *out)
{
if (pan_fb_has_zs(info->fb)) {
emit_zs_crc_desc(info, GENX(pan_fb_get_clean_tile)(info), out->zs_crc);
}
emit_rts(info, out->rts);
return 0;
}
#else /* PAN_ARCH < 14 */
uint32_t
GENX(pan_emit_fb_desc)(const struct pan_fb_desc_info *info,
const struct pan_fb_descs *out)
{
const struct pan_fb_layout *fb = info->fb;
const struct pan_fb_load *load = info->load;
const struct pan_fb_store *store = info->store;
const struct pan_fb_clean_tile ct = GENX(pan_fb_get_clean_tile)(info);
const bool has_zs_crc_ext = pan_fb_has_zs(fb);
struct mali_framebuffer_packed fbd = {};
#if PAN_ARCH <= 5
GENX(pan_emit_tls)(info->tls,
pan_section_ptr(&fbd, FRAMEBUFFER, LOCAL_STORAGE));
#endif
pan_section_pack(&fbd, FRAMEBUFFER, PARAMETERS, cfg) {
#if PAN_ARCH >= 6
cfg.pre_frame_0 = pan_fix_frame_shader_mode(info->frame_shaders.modes[0],
ct.rts || ct.zs || ct.s);
cfg.pre_frame_1 = pan_fix_frame_shader_mode(info->frame_shaders.modes[1],
ct.rts || ct.zs || ct.s);
cfg.post_frame = info->frame_shaders.modes[2];
cfg.frame_shader_dcds = info->frame_shaders.dcd_pointer;
cfg.sample_locations = info->sample_pos_array_pointer;
#endif
#if PAN_ARCH >= 13
/* Enabling prepass without pipelineing is generally not good for
* performance, so disable HSR in that case.
*/
cfg.hsr_prepass_enable = info->allow_hsr_prepass &&
pan_fb_can_pipeline_zs(fb);
cfg.hsr_prepass_interleaving_enable = pan_fb_can_pipeline_zs(fb);
cfg.hsr_prepass_filter_enable = true;
cfg.hsr_hierarchical_optimizations_enable = true;
#endif
#if PAN_ARCH >= 9
/* internal_layer_index is used to select the right primitive list in
* the tiler context, and frame_arg is the value that's passed to the
* fragment shader through r62-r63, which we use to pass gl_Layer. Since
* the layer_idx only takes 8-bits, we might use the extra 56-bits we
* have in frame_argument to pass other information to the fragment
* shader at some point.
*/
assert(info->layer >= info->tiler_ctx->valhall.layer_offset);
cfg.internal_layer_index =
info->layer - info->tiler_ctx->valhall.layer_offset;
cfg.frame_argument = info->layer;
#endif
cfg.width = fb->width_px;
cfg.height = fb->height_px;
const struct pan_fb_bbox fb_area_px =
pan_fb_bbox_from_xywh(0, 0, info->fb->width_px, info->fb->height_px);
assert(pan_fb_bbox_is_valid(info->fb->tiling_area_px));
const struct pan_fb_bbox bbox_px =
pan_fb_bbox_clamp(info->fb->tiling_area_px, fb_area_px);
cfg.bound_min_x = bbox_px.min_x;
cfg.bound_min_y = bbox_px.min_y;
cfg.bound_max_x = bbox_px.max_x;
cfg.bound_max_y = bbox_px.max_y;
cfg.sample_count = fb->sample_count;
cfg.sample_pattern = pan_sample_pattern(fb->sample_count);
/* Ensure we cover the samples on the edge for 16x MSAA */
cfg.tie_break_rule = fb->sample_count == 16 ?
MALI_TIE_BREAK_RULE_MINUS_180_OUT_0_IN :
MALI_TIE_BREAK_RULE_MINUS_180_IN_0_OUT;
cfg.effective_tile_size = fb->tile_size_px;
#if PAN_ARCH >= 9
cfg.point_sprite_coord_origin_max_y = info->sprite_coord_origin_max_y;
cfg.first_provoking_vertex = info->provoking_vertex_first;
#endif
assert(fb->rt_count > 0);
cfg.render_target_count = fb->rt_count;
cfg.color_buffer_allocation = fb->tile_rt_alloc_B;
if (fb->s_format != PIPE_FORMAT_NONE) {
cfg.s_clear =
load && pan_target_has_clear(&load->s) ? load->s.clear.stencil : 0;
cfg.s_write_enable = store && store->s.store;
}
if (fb->z_format != PIPE_FORMAT_NONE) {
cfg.z_internal_format = pan_get_z_internal_format(fb->z_format);
cfg.z_clear =
load && pan_target_has_clear(&load->z) ? load->z.clear.depth : 0;
cfg.z_write_enable = store && store->zs.store;
} else {
/* Default to 24 bit depth if there's no surface. */
cfg.z_internal_format = MALI_Z_INTERNAL_FORMAT_D24;
/* If we want a stencil store and we don't have depth, that should
* happen as a stencil store, not combined depth/stencil.
*/
assert(!store || !store->zs.store);
}
cfg.has_zs_crc_extension = has_zs_crc_ext;
#if PAN_ARCH >= 6
cfg.tiler = PAN_ARCH >= 9 ? info->tiler_ctx->valhall.desc
: info->tiler_ctx->bifrost.desc;
#endif
}
#if PAN_ARCH <= 5
/* TODO: Midgard tiler */
assert(info->tiler_ctx == NULL);
/* All weights set to 0, nothing to do here */
pan_section_pack(&fbd, FRAMEBUFFER, TILER_WEIGHTS, w);
#endif
memcpy(out->fbd, &fbd, sizeof(fbd));
if (has_zs_crc_ext) {
emit_zs_crc_desc(info, ct, out->zs_crc);
}
emit_rts(info, out->rts);
struct mali_framebuffer_pointer_packed tag;
pan_pack(&tag, FRAMEBUFFER_POINTER, cfg) {
cfg.zs_crc_extension_present = has_zs_crc_ext;
cfg.render_target_count = fb->rt_count;
}
return tag.opaque[0];
}
#endif /* PAN_ARCH >= 14 */
#endif /* PAN_ARCH >= 5 */
+665
View File
@@ -0,0 +1,665 @@
/*
* Copyright (C) 2026 Collabora, Ltd.
* Copyright (C) 2026 Arm Ltd.
* SPDX-License-Identifier: MIT
*/
#ifndef __PAN_FB_H
#define __PAN_FB_H
#include "genxml/gen_macros.h"
#include "util/format/u_formats.h"
#include "compiler/shader_enums.h"
struct nir_shader;
struct nir_shader_compiler_options;
struct pan_fb_info;
struct pan_image_view;
#define PAN_MAX_RTS 8
/** Bounding box used by the framebuffer abstraction */
struct pan_fb_bbox {
/** Minimum x/y value */
uint16_t min_x, min_y;
/** Maximum x/y value
*
* Like the hardware, the maximums here are inclusive. A pixel is
* in-bounds if min_x <= x <= max_x and min_y <= y <= max_y.
*/
uint16_t max_x, max_y;
};
static inline bool
pan_fb_bbox_is_valid(struct pan_fb_bbox b)
{
return b.min_x <= b.max_x && b.min_y <= b.max_y;
}
static inline bool
pan_fb_bbox_contains_bbox(struct pan_fb_bbox a, struct pan_fb_bbox b)
{
return a.min_x <= b.min_x && b.max_x <= a.max_x &&
a.min_y <= b.min_y && b.max_y <= a.max_y;
}
static inline bool
pan_fb_bbox_equal(struct pan_fb_bbox a, struct pan_fb_bbox b)
{
return a.min_x == b.min_x && b.max_x == a.max_x &&
a.min_y == b.min_y && b.max_y == a.max_y;
}
static inline struct pan_fb_bbox
pan_fb_bbox_clamp(struct pan_fb_bbox a, struct pan_fb_bbox b)
{
assert(pan_fb_bbox_is_valid(a) && pan_fb_bbox_is_valid(a));
return (struct pan_fb_bbox) {
.min_x = CLAMP(a.min_x, b.min_x, b.max_x),
.min_y = CLAMP(a.min_y, b.min_y, b.max_y),
.max_x = CLAMP(a.max_x, b.min_x, b.max_x),
.max_y = CLAMP(a.max_y, b.min_y, b.max_y),
};
}
static inline struct pan_fb_bbox
pan_fb_bbox_align(struct pan_fb_bbox b, uint32_t align_x, uint32_t align_y)
{
assert(util_is_power_of_two_nonzero(align_x));
assert(util_is_power_of_two_nonzero(align_y));
assert(align_x <= UINT16_MAX && align_y <= UINT16_MAX);
b.min_x = ROUND_DOWN_TO(b.min_x, align_x);
b.min_y = ROUND_DOWN_TO(b.min_y, align_y);
b.max_x = align((uint32_t)b.max_x + 1, align_x) - 1;
b.max_y = align((uint32_t)b.max_y + 1, align_y) - 1;
return b;
}
static inline struct pan_fb_bbox
pan_fb_bbox_from_xywh(uint32_t x, uint32_t y, uint32_t w, uint32_t h)
{
assert(x + w > x && x + w - 1 <= UINT16_MAX);
assert(y + h > y && y + h - 1 <= UINT16_MAX);
return (struct pan_fb_bbox) {
.min_x = x,
.min_y = y,
.max_x = x + w - 1,
.max_y = y + h - 1,
};
}
/** Framebuffer layout
*
* This describes the layout of framebuffer in tile memory as well as various
* properties that are properties of the framebuffer itself, nit any of the
* image views attached to it.
*/
struct pan_fb_layout {
/** Dimensions of the framebuffer itself, in pixels */
uint32_t width_px, height_px;
/** The render area
*
* This is the API render area (as defined by Vulkan) and may be smaller
* than the actual bounding box of the framebuffer. The in_bounds_load
* and border_load members of pan_fb_load_target are relative to the
* render area.
*/
struct pan_fb_bbox render_area_px;
/** The tiling area
*
* This is based on the render area but may be larger to accomodate tiling
* restrictions.
*/
struct pan_fb_bbox tiling_area_px;
/** Sample count for this framebuffer */
uint8_t sample_count;
/** Color render target formats
*
* If a render target is unused, set it to PIPE_FORMAT_NONE.
*/
uint8_t rt_count;
enum pipe_format rt_formats[PAN_MAX_RTS];
/** Depth format for this framebuffer, or PIPE_FORMAT_NONE */
enum pipe_format z_format;
/** Stencil format for this framebuffer, or PIPE_FORMAT_NONE */
enum pipe_format s_format;
/** Amount of space reserved for PLS, in bytes.
*
* This may overlap the color render targets in tile memory.
*/
uint16_t pls_size_B;
uint32_t tile_rt_budget_B;
uint32_t tile_z_budget_B;
/** Optimal tile buffer size, in pixels
*
* This must be a power of two.
*/
unsigned tile_size_px;
/** Amount of tile memory allocated for color render targets */
unsigned tile_rt_alloc_B;
};
static inline bool
pan_fb_has_zs(const struct pan_fb_layout *fb)
{
/* An intentional choice is made here to base has_zs on the formats in
* the pan_fb_layout and not the stores being done in the end. PanVK in
* particular relies on the size of the final framebuffer descriptor being
* the same for all the different variants with incremental rendering.
*/
return fb->z_format != PIPE_FORMAT_NONE ||
fb->s_format != PIPE_FORMAT_NONE;
}
static inline bool
pan_fb_is_fully_covered(const struct pan_fb_layout *fb)
{
const struct pan_fb_bbox fb_area_px =
pan_fb_bbox_from_xywh(0, 0, fb->width_px, fb->height_px);
return pan_fb_bbox_equal(fb->render_area_px, fb_area_px);
}
static inline bool
pan_fb_has_partial_tiles(const struct pan_fb_layout *fb)
{
assert(pan_fb_bbox_contains_bbox(fb->tiling_area_px, fb->render_area_px));
return !pan_fb_bbox_equal(fb->tiling_area_px, fb->render_area_px);
}
#ifdef PAN_ARCH
void GENX(pan_select_fb_tile_size)(struct pan_fb_layout *fb);
#endif
enum ENUM_PACKED pan_fb_load_op {
PAN_FB_LOAD_NONE = 0,
PAN_FB_LOAD_CLEAR,
PAN_FB_LOAD_IMAGE,
PAN_FB_LOAD_OP_COUNT,
};
/** Describes how MSAA is handled when copying
*
* The source or destination may or may not be multisampled independently and
* this controls how that is handled in the copy.
*/
enum ENUM_PACKED pan_fb_msaa_copy_op {
/** Copies all samples of the source to the destination
*
* This requires that the source and destination sample counts be the same.
* If the source and destination are both single-sampled, this is equivalent
* as PAN_FB_MSAA_COPY_SINGLE or PAN_FB_MSAA_COPY_SAMPLE_0.
*/
PAN_FB_MSAA_COPY_ALL = 0,
/** Copies the single sample from the source to the destination
*
* This requires that the source be single-sampled. Semantically, this is
* the same as PAN_FB_MSAA_COPY_SAMPLE_0, but it's useful to make this
* distinction in shader keys so single and multi-sampled sources can be
* differentiated.
*/
PAN_FB_MSAA_COPY_SINGLE,
/** Copies assuming all source samples are identical
*
* The actual copy can use any resolve mode because we know a priori that
* all samples are identical. All resolve modes yield the same result.
*/
PAN_FB_MSAA_COPY_IDENTICAL,
/** Copies sample 0 from the source to all samples in the destination */
PAN_FB_MSAA_COPY_SAMPLE_0,
/** Copies the average of the samples in the source to the destination */
PAN_FB_MSAA_COPY_AVERAGE,
/** Copies the minimum of the samples in the source to the destination */
PAN_FB_MSAA_COPY_MIN,
/** Copies the maximum of the samples in the source to the destination */
PAN_FB_MSAA_COPY_MAX,
PAN_FB_MSAA_COPY_OP_COUNT,
};
struct pan_fb_load_target {
/** Load op for pixels inside the bounding box */
enum pan_fb_load_op in_bounds_load;
/** Load op for border (out of bounds) pixels
*
* This should almost always be LOAD_OP_LOAD, unless you have a very good
* reason to make a different choice.
*/
enum pan_fb_load_op border_load;
/** Always load, even if no primitives intersect the tile
*
* This should usually be set for clears.
*/
bool always;
/** MSAA mode used when load op is IMAGE */
enum pan_fb_msaa_copy_op msaa;
/* Image view to load from, or NULL if neither load op is IMAGE */
const struct pan_image_view *iview;
/** Clear value, if either load op is CLEAR */
union {
union pipe_color_union color;
float depth;
uint8_t stencil;
} clear;
};
static inline struct pan_fb_load_target
pan_fb_load_iview(const struct pan_image_view *iview)
{
return (struct pan_fb_load_target) {
.in_bounds_load = PAN_FB_LOAD_IMAGE,
.border_load = PAN_FB_LOAD_IMAGE,
.msaa = PAN_FB_MSAA_COPY_ALL,
.iview = iview,
};
}
struct pan_fb_load {
/** Color render target loads */
struct pan_fb_load_target rts[PAN_MAX_RTS];
/** Depth target load */
struct pan_fb_load_target z;
/** Stencil target load */
struct pan_fb_load_target s;
};
static inline bool
pan_fb_has_image_load(const struct pan_fb_load *load, bool include_border)
{
if (load->z.in_bounds_load == PAN_FB_LOAD_IMAGE ||
load->s.in_bounds_load == PAN_FB_LOAD_IMAGE)
return true;
if (include_border &&
(load->z.border_load == PAN_FB_LOAD_IMAGE ||
load->s.border_load == PAN_FB_LOAD_IMAGE))
return true;
for (unsigned rt = 0; rt < PAN_MAX_RTS; rt++) {
if (load->rts[rt].in_bounds_load == PAN_FB_LOAD_IMAGE)
return true;
if (include_border && load->rts[rt].border_load == PAN_FB_LOAD_IMAGE)
return true;
}
return false;
}
/** Describes a resolve operation
*
* This describes the source data for the resolve operation. The way in which
* MSAA is handled in the source data is defined by the pan_fb_msaa_copy_op.
*/
enum ENUM_PACKED pan_fb_resolve_op {
/** Do nothing
*
* In this mode, the framebuffer is left unaffected by the resolve. If
* necessary (sometimes required for Z/S), the old value will be read and
* written back to ensure the final value is the same.
*/
PAN_FB_RESOLVE_NONE = 0,
/** Load from the specified image view */
PAN_FB_RESOLVE_IMAGE,
/** Load from color render target 0 */
PAN_FB_RESOLVE_RT_0,
PAN_FB_RESOLVE_RT_1,
PAN_FB_RESOLVE_RT_2,
PAN_FB_RESOLVE_RT_3,
PAN_FB_RESOLVE_RT_4,
PAN_FB_RESOLVE_RT_5,
PAN_FB_RESOLVE_RT_6,
PAN_FB_RESOLVE_RT_7,
/** Load from the depth target
*
* From the perspective of resolve ops, Z/S are always separate
*/
PAN_FB_RESOLVE_Z,
/** Load from the stencil target
*
* From the perspective of resolve ops, Z/S are always separate
*/
PAN_FB_RESOLVE_S,
PAN_FB_RESOLVE_OP_COUNT,
};
static_assert(PAN_FB_RESOLVE_Z == PAN_FB_RESOLVE_RT_0 + PAN_MAX_RTS,
"There are PAN_MAX_RTS many RTs");
#define PAN_FB_RESOLVE_RT(rt) \
(assert(0 <= (rt) && (rt) < PAN_MAX_RTS), (PAN_FB_RESOLVE_RT_0 + (rt)))
/** Describes a resovle operation on a given target
*
* For each side of the render area (in-bounds or border), this defines both
* a source to copy from (the resolve op) and a MSAA copy op to apply as part
* of the copy. In the common case, a resolve op will read from itself but
* that is not strictly a requirement. Any resolve op can read from any
* render target.
*/
struct pan_fb_resolve_target {
struct pan_fb_resolve_op_msaa {
enum pan_fb_resolve_op resolve;
enum pan_fb_msaa_copy_op msaa;
} in_bounds, border;
/** For PAN_FB_RESOLVE_IMAGE the image view to load from */
const struct pan_image_view *iview;
};
/** Describes a resolve operation
*
* A resolve operation is implemented as a post-frame shader (see
* pan_fb_resolve_shader_key_fill) and does a copy from render targets to
* render targets, applying MSAA copy ops along the way.
*/
struct pan_fb_resolve {
struct pan_fb_resolve_target rts[PAN_MAX_RTS];
struct pan_fb_resolve_target z, s;
};
struct pan_fb_store_target {
/** Whether or not to do a store */
bool store;
/** Always store, even if no primitives intersect the tile
*
* clean_tile_write_enable will be set if either store.always is set or
* load.always is set for the corresponding load[s].
*/
bool always;
/** MSAA mode to use when copying to the destination image
*
* This can only be set to AVERAGE for blendable color formats.
*/
enum pan_fb_msaa_copy_op msaa;
/** Image view to store to */
const struct pan_image_view *iview;
};
static inline struct pan_fb_store_target
pan_fb_store_iview(const struct pan_image_view *iview)
{
return (struct pan_fb_store_target) {
.store = true,
.msaa = PAN_FB_MSAA_COPY_ALL,
.iview = iview,
};
}
static inline struct pan_fb_store_target
pan_fb_always_store_iview_s0(const struct pan_image_view *iview)
{
return (struct pan_fb_store_target) {
.store = true,
.always = true,
.msaa = PAN_FB_MSAA_COPY_SAMPLE_0,
.iview = iview,
};
}
struct pan_fb_store {
/* Color render target stores */
struct pan_fb_store_target rts[PAN_MAX_RTS];
/* Depth/stencil target store
*
* If the attached image view has a combined depth/stencil format, both
* depth and stencil will be written.
*/
struct pan_fb_store_target zs;
/* Stencil target store */
struct pan_fb_store_target s;
};
#ifdef PAN_ARCH
void GENX(pan_align_fb_tiling_area)(struct pan_fb_layout *fb,
const struct pan_fb_store *store);
void GENX(pan_fb_fold_resolve_into_store)(const struct pan_fb_layout *fb,
struct pan_fb_resolve *resolve,
struct pan_fb_store *store);
#endif
struct pan_fb_frame_shaders {
uint64_t dcd_pointer;
uint8_t modes[3];
};
struct pan_fb_desc_info {
const struct pan_fb_layout *fb;
const struct pan_fb_load *load;
const struct pan_fb_store *store;
struct pan_fb_frame_shaders frame_shaders;
uint64_t sample_pos_array_pointer;
/* Only used on Valhal */
bool sprite_coord_origin_max_y;
bool provoking_vertex_first;
bool allow_hsr_prepass;
uint16_t layer;
const struct pan_tls_info *tls;
const struct pan_tiler_context *tiler_ctx;
};
#ifdef PAN_ARCH
void GENX(pan_fill_fb_info)(const struct pan_fb_desc_info *info,
struct pan_fb_info *fbinfo);
struct pan_fb_descs {
#if PAN_ARCH <= 13
struct mali_framebuffer_packed *fbd;
#endif
struct mali_zs_crc_extension_packed *zs_crc;
struct mali_rgb_render_target_packed *rts;
};
uint32_t GENX(pan_emit_fb_desc)(const struct pan_fb_desc_info *info,
const struct pan_fb_descs *out);
#endif
enum ENUM_PACKED pan_fb_shader_op {
/** Preserves the current fragment contents
*
* The MSAA mode is ignored and, if a load does need to occur to preserve
* the current fragment contents, PAN_FB_MSAA_COPY_ALL is implied.
*
* This is only allowed in resolve shaders so this can always be replaced
* with a discard.
*/
PAN_FB_SHADER_PRESERVE = 0,
/** Do whatever is the fastest (lowest bandwidth)
*
* This could be a CLEAR or PRESERVE or just write zeros. It will never
* write actual random data but the exact behavior may depend on other bits
* in the shader key.
*/
PAN_FB_SHADER_DONT_CARE,
/** Loads the clear color
*
* This is only allowed in preload shaders and the clear will also be done
* in hardware so it can always be replaced with a discard.
*/
PAN_FB_SHADER_LOAD_CLEAR,
/** Loads from the image bound at the corresponding gl_frag_result */
PAN_FB_SHADER_LOAD_IMAGE,
PAN_FB_SHADER_COPY_RT_0,
PAN_FB_SHADER_COPY_RT_1,
PAN_FB_SHADER_COPY_RT_2,
PAN_FB_SHADER_COPY_RT_3,
PAN_FB_SHADER_COPY_RT_4,
PAN_FB_SHADER_COPY_RT_5,
PAN_FB_SHADER_COPY_RT_6,
PAN_FB_SHADER_COPY_RT_7,
PAN_FB_SHADER_COPY_Z,
PAN_FB_SHADER_COPY_S,
PAN_FB_SHADER_OP_COUNT,
};
static_assert(PAN_FB_SHADER_COPY_Z == PAN_FB_SHADER_COPY_RT_0 + PAN_MAX_RTS,
"We should have one PAN_FB_SHADER_COPY_RT_N per RT");
#define PAN_FB_SHADER_COPY_RT(rt) \
(assert(0 <= (rt) && (rt) < PAN_MAX_RTS), (PAN_FB_SHADER_COPY_RT_0 + (rt)))
static inline bool
pan_fb_shader_op_can_discard(enum pan_fb_shader_op op)
{
return op == PAN_FB_SHADER_PRESERVE ||
op == PAN_FB_SHADER_DONT_CARE ||
op == PAN_FB_SHADER_LOAD_CLEAR;
}
enum ENUM_PACKED pan_fb_shader_data_type {
PAN_FB_SHADER_DATA_TYPE_F32 = 0,
PAN_FB_SHADER_DATA_TYPE_I32,
PAN_FB_SHADER_DATA_TYPE_U32,
PAN_FB_SHADER_DATA_TYPE_COUNT,
};
static_assert(PAN_FB_SHADER_OP_COUNT <= (1 << 4),
"pan_fb_shader_op fits in 4 bits");
static_assert(PAN_FB_MSAA_COPY_OP_COUNT <= (1 << 3),
"pan_fb_msaa_copy_op fits in 3 bits");
static_assert(PAN_FB_SHADER_DATA_TYPE_COUNT <= (1 << 2),
"pan_fb_shader_data_type fits in 2 bits");
/* Asserts for glsl_sampler_dim glsl_base_type have to be runtime because
* there is no MAX value we can use.
*/
PRAGMA_DIAGNOSTIC_PUSH
PRAGMA_DIAGNOSTIC_ERROR(-Wpadded)
struct pan_fb_shader_key_target {
uint32_t in_bounds_op : 4;
uint32_t border_op : 4;
uint32_t in_bounds_msaa : 3;
uint32_t border_msaa : 3;
uint32_t sample0_only : 1;
uint32_t image_dim : 2;
uint32_t image_is_array : 1;
uint32_t image_samples_log2 : 3;
uint32_t data_type : 2;
uint32_t pad : 9;
};
PRAGMA_DIAGNOSTIC_POP
static_assert(sizeof(struct pan_fb_shader_key_target) == 4,
"This struct has no holes");
/** Whether or not the given target is written by the FB shader
*
* GENX(pan_get_fb_shader)() guarantees that if both in_bounds_op and
* border_op can be discarded, that it does nothing with that target. This
* way the full outputs_written can be known from just the shader key.
*/
static inline bool
pan_fb_shader_key_target_written(const struct pan_fb_shader_key_target *target)
{
return !pan_fb_shader_op_can_discard(target->in_bounds_op) ||
!pan_fb_shader_op_can_discard(target->border_op);
}
PRAGMA_DIAGNOSTIC_PUSH
PRAGMA_DIAGNOSTIC_ERROR(-Wpadded)
struct pan_fb_shader_key {
struct pan_fb_shader_key_target rts[PAN_MAX_RTS];
struct pan_fb_shader_key_target z, s;
uint16_t z_format;
uint8_t fb_sample_count;
uint8_t _pad;
};
PRAGMA_DIAGNOSTIC_POP
static_assert(sizeof(struct pan_fb_shader_key) == 4 * (PAN_MAX_RTS + 3),
"This struct has no holes");
#ifdef PAN_ARCH
bool GENX(pan_fb_load_shader_key_fill)(struct pan_fb_shader_key *key,
const struct pan_fb_layout *fb,
const struct pan_fb_load *load,
bool zs_prepass);
#if PAN_ARCH >= 5
struct pan_fb_clean_tile {
uint8_t rts;
bool zs, s;
};
struct pan_fb_clean_tile
GENX(pan_fb_get_clean_tile)(const struct pan_fb_desc_info *info);
static inline bool
pan_target_has_clear(const struct pan_fb_load_target *target)
{
return target->in_bounds_load == PAN_FB_LOAD_CLEAR ||
target->border_load == PAN_FB_LOAD_CLEAR;
}
#endif /* PAN_ARCH >= 5 */
#if PAN_ARCH >= 6
bool GENX(pan_fb_resolve_shader_key_fill)(struct pan_fb_shader_key *key,
const struct pan_fb_layout *fb,
const struct pan_fb_resolve *resolve);
#endif /* PAN_ARCH >= 6 */
struct nir_shader *
GENX(pan_get_fb_shader)(const struct pan_fb_shader_key *key,
const struct nir_shader_compiler_options *nir_options);
#if PAN_ARCH >= 13
/**
* Returns true if there's enough space in the tile buffer for at least two
* Z/S tiles.
*/
static inline bool
pan_fb_can_pipeline_zs(const struct pan_fb_layout *fb)
{
const uint32_t z_B_per_px = sizeof(float) * fb->sample_count;
const uint32_t z_B_per_tile = z_B_per_px * fb->tile_size_px;
/* The budget is already half the available Z space */
return z_B_per_tile < fb->tile_z_budget_B;
}
#endif
#endif /* PAN_ARCH */
#endif /* __PAN_FB_H */
+859
View File
@@ -0,0 +1,859 @@
/*
* Copyright (C) 2026 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#include "pan_blend.h"
#include "pan_desc.h"
#include "pan_fb.h"
#include "compiler/nir/nir.h"
#include "compiler/nir/nir_builder.h"
#include "compiler/pan_nir.h"
static enum glsl_sampler_dim
mali_to_glsl_dim(enum mali_texture_dimension dim)
{
switch (dim) {
case MALI_TEXTURE_DIMENSION_1D: return GLSL_SAMPLER_DIM_1D;
case MALI_TEXTURE_DIMENSION_CUBE:
case MALI_TEXTURE_DIMENSION_2D: return GLSL_SAMPLER_DIM_2D;
case MALI_TEXTURE_DIMENSION_3D: return GLSL_SAMPLER_DIM_3D;
default:
UNREACHABLE("Unknown mali_texture_dimension");
}
}
static enum pan_fb_shader_data_type
data_type_for_format(enum pipe_format format)
{
if (util_format_is_pure_uint(format))
return PAN_FB_SHADER_DATA_TYPE_U32;
else if (util_format_is_pure_sint(format))
return PAN_FB_SHADER_DATA_TYPE_I32;
else
return PAN_FB_SHADER_DATA_TYPE_F32;
}
static nir_alu_type
nir_alu_type_for_data_type(enum pan_fb_shader_data_type data_type)
{
switch (data_type) {
case PAN_FB_SHADER_DATA_TYPE_F32: return nir_type_float32;
case PAN_FB_SHADER_DATA_TYPE_I32: return nir_type_int32;
case PAN_FB_SHADER_DATA_TYPE_U32: return nir_type_uint32;
default:
UNREACHABLE("Invalid pan_fb_shader_data_type");
}
}
static enum pan_fb_shader_op
get_shader_op_for_load(enum pan_fb_load_op op)
{
switch (op) {
case PAN_FB_LOAD_NONE: return PAN_FB_SHADER_DONT_CARE;
case PAN_FB_LOAD_CLEAR: return PAN_FB_SHADER_LOAD_CLEAR;
case PAN_FB_LOAD_IMAGE: return PAN_FB_SHADER_LOAD_IMAGE;
default:
UNREACHABLE("Invalid load op");
}
}
static inline enum pan_fb_shader_op
get_shader_op_for_resolve(enum pan_fb_resolve_op op)
{
switch (op) {
case PAN_FB_RESOLVE_NONE: return PAN_FB_SHADER_PRESERVE;
case PAN_FB_RESOLVE_IMAGE: return PAN_FB_SHADER_LOAD_IMAGE;
case PAN_FB_RESOLVE_RT_0: return PAN_FB_SHADER_COPY_RT_0;
case PAN_FB_RESOLVE_RT_1: return PAN_FB_SHADER_COPY_RT_1;
case PAN_FB_RESOLVE_RT_2: return PAN_FB_SHADER_COPY_RT_2;
case PAN_FB_RESOLVE_RT_3: return PAN_FB_SHADER_COPY_RT_3;
case PAN_FB_RESOLVE_RT_4: return PAN_FB_SHADER_COPY_RT_4;
case PAN_FB_RESOLVE_RT_5: return PAN_FB_SHADER_COPY_RT_5;
case PAN_FB_RESOLVE_RT_6: return PAN_FB_SHADER_COPY_RT_6;
case PAN_FB_RESOLVE_RT_7: return PAN_FB_SHADER_COPY_RT_7;
case PAN_FB_RESOLVE_Z: return PAN_FB_SHADER_COPY_Z;
case PAN_FB_RESOLVE_S: return PAN_FB_SHADER_COPY_S;
default:
UNREACHABLE("Invalid resolve op");
}
}
static enum pan_fb_msaa_copy_op
reduce_msaa_op(enum pan_fb_msaa_copy_op msaa, enum pan_fb_shader_op op,
uint8_t fb_sample_count, uint8_t image_sample_count)
{
if (pan_fb_shader_op_can_discard(op))
return PAN_FB_MSAA_COPY_ALL;
const uint8_t src_sample_count = op == PAN_FB_SHADER_LOAD_IMAGE
? image_sample_count : fb_sample_count;
if (msaa == PAN_FB_MSAA_COPY_ALL)
assert(src_sample_count == fb_sample_count);
if (src_sample_count <= 1)
return PAN_FB_MSAA_COPY_SINGLE;
return msaa;
}
static bool
op_needs_sample_count(enum pan_fb_shader_op op,
enum pan_fb_msaa_copy_op msaa)
{
return msaa == PAN_FB_MSAA_COPY_AVERAGE ||
msaa == PAN_FB_MSAA_COPY_MIN ||
msaa == PAN_FB_MSAA_COPY_MAX;
}
static const struct pan_fb_shader_key_target key_target_dont_care = {
.in_bounds_op = PAN_FB_SHADER_DONT_CARE,
.border_op = PAN_FB_SHADER_DONT_CARE,
};
static const struct pan_fb_shader_key_target key_target_preserve = {
.in_bounds_op = PAN_FB_SHADER_PRESERVE,
.border_op = PAN_FB_SHADER_PRESERVE,
};
static struct pan_fb_shader_key_target
get_key_target(enum pipe_format format,
uint8_t fb_sample_count,
bool has_border,
enum pan_fb_shader_op in_bounds_op,
enum pan_fb_shader_op border_op,
enum pan_fb_msaa_copy_op in_bounds_msaa,
enum pan_fb_msaa_copy_op border_msaa,
bool sample0_only,
const struct pan_image_view *iview)
{
if (format == PIPE_FORMAT_NONE)
return key_target_dont_care;
/* If we have a full framebuffer, there is no border. Set the boarder load
* equal to the in-bounds load so things in the shader fold nicely.
*/
if (!has_border) {
border_op = in_bounds_op;
border_msaa = in_bounds_msaa;
}
if (in_bounds_op == PAN_FB_SHADER_DONT_CARE &&
border_op == PAN_FB_SHADER_DONT_CARE)
return key_target_dont_care;
/* If both load ops can be implemented as a discard, set the target to
* always PRESERVE and we just won't emit any code to do a load.
*
* Importantly, this is true for all load ops except PAN_FB_LOAD_OP_IMAGE.
*/
if (pan_fb_shader_op_can_discard(in_bounds_op) &&
pan_fb_shader_op_can_discard(border_op))
return key_target_preserve;
/* We don't have a bound on this enum so we need a live assert */
enum mali_texture_dimension dim = MALI_TEXTURE_DIMENSION_2D;
bool is_array = false;
uint8_t image_sample_count = 0;
if (in_bounds_op == PAN_FB_SHADER_LOAD_IMAGE ||
border_op == PAN_FB_SHADER_LOAD_IMAGE) {
if (util_format_has_depth(util_format_description(format)))
assert(util_format_get_depth_bits(format) ==
util_format_get_depth_bits(iview->format));
else if (util_format_has_stencil(util_format_description(format)))
assert(util_format_has_stencil(util_format_description(iview->format)));
else
assert(format == iview->format);
dim = iview->dim;
is_array = iview->first_layer != iview->last_layer;
image_sample_count = pan_image_view_get_nr_samples(iview);
}
in_bounds_msaa = reduce_msaa_op(in_bounds_msaa, in_bounds_op,
fb_sample_count, image_sample_count);
border_msaa = reduce_msaa_op(border_msaa, border_op,
fb_sample_count, image_sample_count);
const bool needs_sample_count =
op_needs_sample_count(in_bounds_op, in_bounds_msaa) ||
op_needs_sample_count(border_op, border_msaa);
assert(((unsigned)dim) < (1 << 2));
return (struct pan_fb_shader_key_target) {
.in_bounds_op = in_bounds_op,
.border_op = border_op,
.in_bounds_msaa = in_bounds_msaa,
.border_msaa = border_msaa,
.sample0_only = sample0_only,
.image_dim = dim,
.image_is_array = is_array,
.image_samples_log2 = needs_sample_count
? util_logbase2(image_sample_count) : 0,
.data_type = data_type_for_format(format),
};
}
static struct pan_fb_shader_key_target
get_load_key_target(enum pipe_format format,
uint8_t fb_sample_count,
bool has_border,
const struct pan_fb_load_target *load)
{
return get_key_target(format, fb_sample_count, has_border,
get_shader_op_for_load(load->in_bounds_load),
get_shader_op_for_load(load->border_load),
load->msaa, load->msaa, false, load->iview);
}
bool
GENX(pan_fb_load_shader_key_fill)(struct pan_fb_shader_key *key,
const struct pan_fb_layout *fb,
const struct pan_fb_load *load,
bool zs_prepass)
{
*key = (struct pan_fb_shader_key) { };
const bool has_border = pan_fb_has_partial_tiles(fb);
/* Z/S can only be written from the prepass and color can only be written
* from the non-prepass. Setting everything to zero will cause the shader
* to just not write anything in that case.
*/
if (zs_prepass) {
key->z = get_load_key_target(fb->z_format, fb->sample_count,
has_border, &load->z);
key->s = get_load_key_target(fb->s_format, fb->sample_count,
has_border, &load->s);
return pan_fb_shader_key_target_written(&key->z) ||
pan_fb_shader_key_target_written(&key->s);
} else {
bool needs_shader = false;
for (unsigned rt = 0; rt < fb->rt_count; rt++) {
key->rts[rt] = get_load_key_target(fb->rt_formats[rt],
fb->sample_count,
has_border, &load->rts[rt]);
if (pan_fb_shader_key_target_written(&key->rts[rt]))
needs_shader = true;
}
return needs_shader;
}
}
#if PAN_ARCH >= 6
static struct pan_fb_shader_key_target
get_resolve_key_target(enum pipe_format format,
uint8_t fb_sample_count,
bool has_border,
const struct pan_fb_resolve_target *load)
{
const enum pan_fb_shader_op in_bounds_op =
get_shader_op_for_resolve(load->in_bounds.resolve);
const enum pan_fb_shader_op border_op =
get_shader_op_for_resolve(load->border.resolve);
return get_key_target(format, fb_sample_count, has_border,
in_bounds_op, border_op,
load->in_bounds.msaa, load->border.msaa,
true, load->iview);
}
bool
GENX(pan_fb_resolve_shader_key_fill)(struct pan_fb_shader_key *key,
const struct pan_fb_layout *fb,
const struct pan_fb_resolve *resolve)
{
const bool has_border = pan_fb_has_partial_tiles(fb);
*key = (struct pan_fb_shader_key) {
.z = get_resolve_key_target(fb->z_format, fb->sample_count,
has_border, &resolve->z),
.s = get_resolve_key_target(fb->s_format, fb->sample_count,
has_border, &resolve->s),
.z_format = fb->z_format,
.fb_sample_count = fb->sample_count,
};
bool needs_shader = pan_fb_shader_key_target_written(&key->z) ||
pan_fb_shader_key_target_written(&key->s);
for (unsigned rt = 0; rt < fb->rt_count; rt++) {
key->rts[rt] = get_resolve_key_target(fb->rt_formats[rt],
fb->sample_count,
has_border,
&resolve->rts[rt]);
if (pan_fb_shader_key_target_written(&key->rts[rt]))
needs_shader = true;
}
return needs_shader;
}
#endif
static nir_def *
combine_samples_no_div(nir_builder *b, nir_def **samples, uint8_t sample_count,
const nir_alu_type nir_type,
enum pan_fb_msaa_copy_op msaa)
{
assert(util_is_power_of_two_nonzero(sample_count));
if (sample_count == 1)
return samples[0];
nir_def *lo = combine_samples_no_div(b, samples,
sample_count / 2, nir_type, msaa);
nir_def *hi = combine_samples_no_div(b, samples + (sample_count / 2),
sample_count / 2, nir_type, msaa);
/* We assume that first half always comes before the second so setting the
* cursor after the second half combine instruction will give us the least
* common ancestor.
*/
b->cursor = nir_after_instr(nir_def_instr(hi));
switch (msaa) {
case PAN_FB_MSAA_COPY_AVERAGE:
assert(nir_alu_type_get_base_type(nir_type) == nir_type_float);
return nir_fadd(b, lo, hi);
case PAN_FB_MSAA_COPY_MIN:
switch (nir_alu_type_get_base_type(nir_type)) {
case nir_type_float:
return nir_fmin(b, lo, hi);
case nir_type_uint:
return nir_umin(b, lo, hi);
case nir_type_int:
return nir_imin(b, lo, hi);
default:
UNREACHABLE("Unsupported NIR type");
}
case PAN_FB_MSAA_COPY_MAX:
switch (nir_alu_type_get_base_type(nir_type)) {
case nir_type_float:
return nir_fmax(b, lo, hi);
case nir_type_uint:
return nir_umax(b, lo, hi);
case nir_type_int:
return nir_imax(b, lo, hi);
default:
UNREACHABLE("Unsupported NIR type");
}
default:
UNREACHABLE("Invalid MSAA op");
}
}
static nir_def *
combine_samples(nir_builder *b, nir_def **samples, uint8_t sample_count,
const nir_alu_type nir_type, enum pan_fb_msaa_copy_op msaa)
{
if (msaa == PAN_FB_MSAA_COPY_SAMPLE_0)
return samples[0];
nir_def *val = combine_samples_no_div(b, samples, sample_count,
nir_type, msaa);
if (msaa == PAN_FB_MSAA_COPY_AVERAGE)
val = nir_fdiv_imm(b, val, sample_count);
return val;
}
static nir_def *
build_sample_id(nir_builder *b)
{
b->shader->info.fs.uses_sample_shading = true;
return nir_load_sample_id(b);
}
static nir_def *
build_image_load(nir_builder *b, const nir_alu_type nir_type,
nir_def *pos, gl_frag_result location,
enum pan_fb_msaa_copy_op msaa, uint8_t sample_count,
enum mali_texture_dimension dim, bool is_array)
{
assert(pos->num_components == 3);
switch (dim) {
case MALI_TEXTURE_DIMENSION_1D:
if (is_array)
pos = nir_channels(b, pos, 0b101);
else
pos = nir_channel(b, pos, 0);
break;
case MALI_TEXTURE_DIMENSION_CUBE:
assert(is_array);
break;
case MALI_TEXTURE_DIMENSION_2D:
if (!is_array)
pos = nir_channels(b, pos, 0b011);
break;
case MALI_TEXTURE_DIMENSION_3D:
break;
default:
UNREACHABLE("Unsupported dim");
}
nir_def *val;
switch (msaa) {
case PAN_FB_MSAA_COPY_SINGLE:
val = nir_txf(b, pos,
.texture_index = location,
.dim = mali_to_glsl_dim(dim),
.dest_type = nir_type,
.is_array = is_array);
break;
case PAN_FB_MSAA_COPY_ALL:
case PAN_FB_MSAA_COPY_IDENTICAL:
case PAN_FB_MSAA_COPY_SAMPLE_0: {
assert(dim == MALI_TEXTURE_DIMENSION_2D);
nir_def *sample_id = msaa == PAN_FB_MSAA_COPY_ALL
? build_sample_id(b) : nir_imm_int(b, 0);
val = nir_txf_ms(b, pos, sample_id,
.texture_index = location,
.dim = GLSL_SAMPLER_DIM_MS,
.dest_type = nir_type,
.is_array = is_array);
break;
}
case PAN_FB_MSAA_COPY_AVERAGE:
case PAN_FB_MSAA_COPY_MIN:
case PAN_FB_MSAA_COPY_MAX: {
assert(dim == MALI_TEXTURE_DIMENSION_2D);
assert(sample_count > 0);
nir_def *samples[16];
assert(sample_count <= ARRAY_SIZE(samples));
for (uint32_t s = 0; s < sample_count; s++) {
samples[s] = nir_txf_ms(b, pos, nir_imm_int(b, s),
.texture_index = location,
.dim = GLSL_SAMPLER_DIM_MS,
.dest_type = nir_type,
.is_array = is_array);
}
val = combine_samples(b, samples, sample_count, nir_type, msaa);
break;
}
default:
UNREACHABLE("Invalid copy op");
}
if (location == FRAG_RESULT_DEPTH || location == FRAG_RESULT_STENCIL)
val = nir_channel(b, val, 0);
return val;
}
#if PAN_ARCH >= 6
static uint32_t
get_fb_conversion(nir_alu_type nir_type, enum pipe_format format)
{
#if PAN_ARCH >= 9
return 0;
#else
struct mali_internal_conversion_packed conv;
pan_pack(&conv, INTERNAL_CONVERSION, cfg) {
cfg.register_format = pan_blend_type_from_nir(nir_type);
cfg.memory_format = GENX(pan_dithered_format_from_pipe_format)(
format, false);
}
return conv.opaque[0];
#endif
}
static nir_def *
build_single_fb_load(nir_builder *b, const nir_alu_type nir_type,
gl_frag_result location, nir_def *sample,
enum pipe_format z_format)
{
const bool is_zs = location == FRAG_RESULT_DEPTH ||
location == FRAG_RESULT_STENCIL;
unsigned num_components = is_zs ? 1 : 4;
nir_def *conversion;
if (location == FRAG_RESULT_DEPTH) {
conversion = nir_imm_int(b, get_fb_conversion(nir_type, z_format));
} else if (location == FRAG_RESULT_STENCIL) {
conversion = nir_imm_int(b,
get_fb_conversion(nir_type, PIPE_FORMAT_S8_UINT));
} else {
assert(location >= FRAG_RESULT_DATA0);
uint8_t rt = location - FRAG_RESULT_DATA0;
assert(rt < PAN_MAX_RTS);
conversion = nir_unpack_64_2x32_split_y(b,
nir_load_blend_descriptor_pan(b, .base = rt));
}
nir_def *val = nir_load_tile_pan(b,
num_components, 32,
pan_nir_tile_location_sample(b, location, sample),
pan_nir_tile_default_coverage(b),
conversion,
.dest_type = nir_type,
.io_semantics.location = location,
.io_semantics.num_slots = 1);
/* If we loaded the stencil value, the upper 24 bits might contain garbage,
* so we need to mask that off before we do any math on the result.
*/
if (location == FRAG_RESULT_STENCIL)
val = nir_iand_imm(b, val, 0xff);
return val;
}
static nir_def *
build_fb_load(nir_builder *b, const nir_alu_type nir_type,
gl_frag_result location, enum pipe_format z_format,
enum pan_fb_msaa_copy_op msaa, uint8_t fb_sample_count)
{
switch (msaa) {
case PAN_FB_MSAA_COPY_SINGLE:
case PAN_FB_MSAA_COPY_ALL:
case PAN_FB_MSAA_COPY_IDENTICAL:
case PAN_FB_MSAA_COPY_SAMPLE_0: {
nir_def *sample_id = msaa == PAN_FB_MSAA_COPY_ALL
? build_sample_id(b) : nir_imm_int(b, 0);
return build_single_fb_load(b, nir_type, location, sample_id, z_format);
}
case PAN_FB_MSAA_COPY_AVERAGE:
case PAN_FB_MSAA_COPY_MIN:
case PAN_FB_MSAA_COPY_MAX: {
nir_def *samples[16];
assert(fb_sample_count > 0 && fb_sample_count <= ARRAY_SIZE(samples));
for (uint32_t s = 0; s < fb_sample_count; s++) {
samples[s] = build_single_fb_load(b, nir_type, location,
nir_imm_int(b, s), z_format);
}
return combine_samples(b, samples, fb_sample_count, nir_type, msaa);
}
default:
UNREACHABLE("Invalid copy op");
}
}
#endif
static nir_def *
build_load(nir_builder *b, nir_def *pos,
enum pan_fb_shader_op op, enum pan_fb_msaa_copy_op msaa,
gl_frag_result location,
const struct pan_fb_shader_key_target *target,
const struct pan_fb_shader_key *key)
{
const nir_alu_type nir_type = nir_alu_type_for_data_type(target->data_type);
const bool is_zs = location == FRAG_RESULT_DEPTH ||
location == FRAG_RESULT_STENCIL;
switch (op) {
#if PAN_ARCH >= 6
case PAN_FB_SHADER_PRESERVE:
return build_fb_load(b, nir_type, location, key->z_format,
PAN_FB_MSAA_COPY_ALL, key->fb_sample_count);
#endif
case PAN_FB_SHADER_DONT_CARE:
return nir_imm_zero(b, is_zs ? 1 : 4, 32);
case PAN_FB_SHADER_LOAD_CLEAR:
return nir_load_clear_value_pan(b, is_zs ? 1 : 4, 32,
.io_semantics.location = location,
.dest_type = nir_type);
case PAN_FB_SHADER_LOAD_IMAGE:
return build_image_load(b, nir_type, pos, location, msaa,
1 << target->image_samples_log2,
target->image_dim, target->image_is_array);
#if PAN_ARCH >= 6
default: { /* PAN_FB_SHADER_COPY_RT_N */
uint8_t rt = op - PAN_FB_SHADER_COPY_RT_0;
assert(rt < PAN_MAX_RTS);
return build_fb_load(b, nir_type, FRAG_RESULT_DATA0 + rt,
PIPE_FORMAT_NONE, msaa, key->fb_sample_count);
}
case PAN_FB_SHADER_COPY_Z:
return build_fb_load(b, nir_type, FRAG_RESULT_DEPTH,
key->z_format, msaa, key->fb_sample_count);
case PAN_FB_SHADER_COPY_S:
return build_fb_load(b, nir_type, FRAG_RESULT_STENCIL,
PIPE_FORMAT_S8_UINT, msaa, key->fb_sample_count);
#else
default:
UNREACHABLE("Unsupported shader op");
#endif
}
}
static void
build_op(nir_builder *b, nir_def *pos, nir_def *in_bounds,
gl_frag_result location,
const struct pan_fb_shader_key_target *target,
const struct pan_fb_shader_key *key)
{
nir_if *nif_s0 = NULL;
if (target->sample0_only)
nif_s0 = nir_push_if(b, nir_ieq_imm(b, build_sample_id(b), 0));
nir_def *val;
if (target->in_bounds_op == target->border_op &&
target->in_bounds_msaa == target->border_msaa) {
val = build_load(b, pos, target->in_bounds_op, target->in_bounds_msaa,
location, target, key);
} else {
nir_def *in_bounds_val, *border_val;
nir_if *nif = nir_push_if(b, in_bounds);
{
in_bounds_val = build_load(b, pos, target->in_bounds_op,
target->in_bounds_msaa,
location, target, key);
}
nir_push_else(b, nif);
{
border_val = build_load(b, pos, target->border_op,
target->border_msaa,
location, target, key);
}
nir_pop_if(b, nif);
val = nir_if_phi(b, in_bounds_val, border_val);
}
nir_store_output(b, val, nir_imm_int(b, 0),
.base = location, .range = 1,
.write_mask = nir_component_mask(val->num_components),
.src_type = nir_alu_type_for_data_type(target->data_type),
.io_semantics.location = location,
.io_semantics.num_slots = 1);
if (nif_s0)
nir_pop_if(b, nif_s0);
}
struct pan_fb_shader_info {
bool discard_in_bounds;
bool discard_border;
bool sample0_only;
};
static void
gather_target_info(struct pan_fb_shader_info *info,
const struct pan_fb_shader_key_target *target)
{
/* Ignore any targets we don't write */
if (!pan_fb_shader_key_target_written(target))
return;
if (!pan_fb_shader_op_can_discard(target->in_bounds_op))
info->discard_in_bounds = false;
if (!pan_fb_shader_op_can_discard(target->border_op))
info->discard_border = false;
if (!target->sample0_only)
info->sample0_only = false;
}
static struct pan_fb_shader_info
gather_shader_info(const struct pan_fb_shader_key *key)
{
struct pan_fb_shader_info info = {
.discard_in_bounds = true,
.discard_border = true,
.sample0_only = true,
};
for (unsigned rt = 0; rt < PAN_MAX_RTS; rt++)
gather_target_info(&info, &key->rts[rt]);
gather_target_info(&info, &key->z);
gather_target_info(&info, &key->s);
return info;
}
static bool
opt_sample0_only_intr(nir_builder *b, nir_intrinsic_instr *intr, void *data)
{
switch (intr->intrinsic) {
case nir_intrinsic_load_sample_id:
b->cursor = nir_before_instr(&intr->instr);
nir_def_replace(&intr->def, nir_imm_int(b, 0));
return true;
case nir_intrinsic_load_cumulative_coverage_pan:
b->cursor = nir_before_instr(&intr->instr);
nir_def_replace(&intr->def, nir_imm_int(b, 1));
return true;
default:
return false;
}
}
nir_shader *
GENX(pan_get_fb_shader)(const struct pan_fb_shader_key *key,
const struct nir_shader_compiler_options *nir_options)
{
nir_builder builder = nir_builder_init_simple_shader(
MESA_SHADER_FRAGMENT, nir_options, "pan-fb-load");
nir_builder *b = &builder;
const struct pan_fb_shader_info info = gather_shader_info(key);
/* We shouldn't even be building a shader at all in this case but, on the
* off chance that we do, build one that just discards.
*/
if (info.discard_in_bounds && info.discard_border) {
nir_terminate(b);
return builder.shader;
}
nir_def *pos_xy = nir_load_pixel_coord(b);
nir_def *pos_x = nir_u2u32(b, nir_channel(b, pos_xy, 0));
nir_def *pos_y = nir_u2u32(b, nir_channel(b, pos_xy, 1));
nir_def *pos = nir_vec3(b, pos_x, pos_y, nir_load_layer_id(b));
nir_def *ra = nir_load_fb_render_area_pan(b);
nir_def *ra_min_xy = nir_channels(b, ra, 0b0011);
nir_def *ra_max_xy = nir_channels(b, ra, 0b1100);
nir_def *in_bounds =
nir_ball(b, nir_iand(b, nir_uge(b, pos_xy, ra_min_xy),
nir_uge(b, ra_max_xy, pos_xy)));
if (PAN_ARCH >= 6 && !pan_fb_shader_key_target_written(&key->z) &&
!pan_fb_shader_key_target_written(&key->s)) {
/* We assume that the framebuffer will clear if either load op is set to
* clear. For color-only on bifrost, we can do a bit better by emitting
* BLEND instructions directly only writing the tile buffer if we're
* actually loading the image.
*/
for (unsigned rt = 0; rt < PAN_MAX_RTS; rt++) {
const struct pan_fb_shader_key_target *target = &key->rts[rt];
const gl_frag_result location = FRAG_RESULT_DATA0 + rt;
if (!pan_fb_shader_key_target_written(target))
continue;
nir_def *coverage = nir_load_cumulative_coverage_pan(b);
nir_def *z1 = nir_imm_int(b, 0);
nir_def *u4 = nir_undef(b, 4, 32);
nir_if *nif_s0 = NULL;
if (target->sample0_only)
nif_s0 = nir_push_if(b, nir_ieq_imm(b, build_sample_id(b), 0));
nir_def *color;
if (target->in_bounds_op == target->border_op &&
target->in_bounds_msaa == target->border_msaa) {
color = build_load(b, pos, target->in_bounds_op,
target->in_bounds_msaa,
location, target, key);
} else {
nir_def *ib_color, *bd_color, *ib_cov, *bd_cov;
nir_if *nif = nir_push_if(b, in_bounds);
{
if (pan_fb_shader_op_can_discard(target->in_bounds_op)) {
ib_color = u4;
ib_cov = z1;
} else {
ib_color = build_load(b, pos, target->in_bounds_op,
target->in_bounds_msaa,
location, target, key);
ib_cov = coverage;
}
}
nir_push_else(b, nif);
{
if (pan_fb_shader_op_can_discard(target->border_op)) {
bd_color = u4;
bd_cov = z1;
} else {
bd_color = build_load(b, pos, target->border_op,
target->border_msaa,
location, target, key);
bd_cov = coverage;
}
}
nir_pop_if(b, nif);
color = nir_if_phi(b, ib_color, bd_color);
coverage = nir_if_phi(b, ib_cov, bd_cov);
}
if (nif_s0) {
nir_pop_if(b, nif_s0);
color = nir_if_phi(b, color, u4);
coverage = nir_if_phi(b, coverage, z1);
}
const nir_alu_type nir_type =
nir_alu_type_for_data_type(target->data_type);
nir_def *blend = nir_load_blend_descriptor_pan(b, .base = rt);
nir_blend_pan(b, coverage, blend, color,
.src_type = nir_type,
.io_semantics.location = location,
.io_semantics.num_slots = 1);
}
} else {
if (info.discard_in_bounds) {
nir_terminate_if(b, in_bounds);
in_bounds = nir_imm_false(b);
} else if (info.discard_border) {
nir_terminate_if(b, nir_inot(b, in_bounds));
in_bounds = nir_imm_true(b);
}
if (info.sample0_only) {
/* The little optimization we do at the end will take care of most
* of what we need for sample0-only but we also need to write
* SAMPLE_MASK so pan_nir_lower_fs_outputs will do the right thing.
*/
nir_store_output(b, nir_imm_int(b, 1), nir_imm_int(b, 0),
.base = FRAG_RESULT_SAMPLE_MASK, .range = 1,
.write_mask = 1,
.src_type = nir_type_uint32,
.io_semantics.location = FRAG_RESULT_SAMPLE_MASK,
.io_semantics.num_slots = 1);
}
if (pan_fb_shader_key_target_written(&key->z)) {
assert(key->z.sample0_only == info.sample0_only);
build_op(b, pos, in_bounds, FRAG_RESULT_DEPTH, &key->z, key);
}
if (pan_fb_shader_key_target_written(&key->s)) {
assert(key->s.sample0_only == info.sample0_only);
build_op(b, pos, in_bounds, FRAG_RESULT_STENCIL, &key->s, key);
}
for (unsigned rt = 0; rt < PAN_MAX_RTS; rt++) {
if (pan_fb_shader_key_target_written(&key->rts[rt])) {
assert(key->rts[rt].sample0_only == info.sample0_only);
build_op(b, pos, in_bounds, FRAG_RESULT_DATA0 + rt,
&key->rts[rt], key);
}
}
}
if (info.sample0_only) {
NIR_PASS(_, b->shader, nir_shader_intrinsics_pass,
opt_sample0_only_intr, nir_metadata_control_flow, NULL);
b->shader->info.fs.uses_sample_shading = false;
}
return builder.shader;
}
+691
View File
@@ -0,0 +1,691 @@
/*
* Copyright (C) 2019 Collabora, Ltd.
* Copyright (C) 2026 Arm Ltd.
* SPDX-License-Identifier: MIT
*/
#include "pan_format.h"
#include "genxml/gen_macros.h"
#include "util/format/u_format.h"
/* Convenience */
#if PAN_ARCH == 6
#define MALI_RGBA_SWIZZLE PAN_V6_SWIZZLE(R, G, B, A)
#define MALI_RGB1_SWIZZLE PAN_V6_SWIZZLE(R, G, B, A)
#else
#define MALI_RGBA_SWIZZLE MALI_RGB_COMPONENT_ORDER_RGBA
#define MALI_RGB1_SWIZZLE MALI_RGB_COMPONENT_ORDER_RGB1
#endif
#define MALI_BLEND_AU_R8G8B8A8 (MALI_RGBA8_TB << 12) | MALI_RGBA_SWIZZLE
#define MALI_BLEND_PU_R8G8B8A8 (MALI_RGBA8_TB << 12) | MALI_RGBA_SWIZZLE
#define MALI_BLEND_AU_R10G10B10A2 (MALI_RGB10_A2_TB << 12) | MALI_RGBA_SWIZZLE
#define MALI_BLEND_PU_R10G10B10A2 (MALI_RGB10_A2_TB << 12) | MALI_RGBA_SWIZZLE
#define MALI_BLEND_AU_R8G8B8A2 (MALI_RGB8_A2_AU << 12) | MALI_RGBA_SWIZZLE
#define MALI_BLEND_PU_R8G8B8A2 (MALI_RGB8_A2_PU << 12) | MALI_RGBA_SWIZZLE
#define MALI_BLEND_AU_R4G4B4A4 (MALI_RGBA4_AU << 12) | MALI_RGBA_SWIZZLE
#define MALI_BLEND_PU_R4G4B4A4 (MALI_RGBA4_PU << 12) | MALI_RGBA_SWIZZLE
#define MALI_BLEND_AU_R5G6B5A0 (MALI_R5G6B5_AU << 12) | MALI_RGB1_SWIZZLE
#define MALI_BLEND_PU_R5G6B5A0 (MALI_R5G6B5_PU << 12) | MALI_RGB1_SWIZZLE
#define MALI_BLEND_AU_R5G5B5A1 (MALI_RGB5_A1_AU << 12) | MALI_RGBA_SWIZZLE
#define MALI_BLEND_PU_R5G5B5A1 (MALI_RGB5_A1_PU << 12) | MALI_RGBA_SWIZZLE
#define MALI_BLEND_R16F (MALI_R16F << 12) | MALI_RGBA_SWIZZLE
#define MALI_BLEND_RG16F (MALI_RG16F << 12) | MALI_RGBA_SWIZZLE
#define MALI_BLEND_RGBA16F (MALI_RGBA16F << 12) | MALI_RGBA_SWIZZLE
#define MALI_BLEND_RG11F_B10F (MALI_R11F_G11F_B10F << 12) | MALI_RGBA_SWIZZLE
#if PAN_ARCH <= 5
#define BFMT2(pipe, internal, writeback, srgb) \
[PIPE_FORMAT_##pipe] = { \
MALI_COLOR_BUFFER_INTERNAL_FORMAT_##internal, \
MALI_COLOR_FORMAT_##writeback, \
{ 0, 0 }, \
}
#else
#define BFMT2(pipe, internal, writeback, srgb) \
[PIPE_FORMAT_##pipe] = { \
MALI_COLOR_BUFFER_INTERNAL_FORMAT_##internal, \
MALI_COLOR_FORMAT_##writeback, \
{ \
MALI_BLEND_PU_##internal | (srgb ? (1 << 20) : 0), \
MALI_BLEND_AU_##internal | (srgb ? (1 << 20) : 0), \
}, \
}
#endif
#define BFMT(pipe, internal_and_writeback) \
BFMT2(pipe, internal_and_writeback, internal_and_writeback, 0)
#define BFMT_SRGB(pipe, writeback) \
BFMT2(pipe##_UNORM, R8G8B8A8, writeback, 0), \
BFMT2(pipe##_SRGB, R8G8B8A8, writeback, 1)
#if PAN_ARCH >= 9
#define BFMT_FLOAT(pipe, internal_and_writeback) \
[PIPE_FORMAT_##pipe] = { \
MALI_COLOR_BUFFER_INTERNAL_FORMAT_##internal_and_writeback, \
MALI_FLOAT_COLOR_FORMAT_##internal_and_writeback, \
{ \
MALI_BLEND_##internal_and_writeback, \
MALI_BLEND_##internal_and_writeback, \
}, \
}
#endif
const struct pan_blendable_format
GENX(pan_blendable_formats)[PIPE_FORMAT_COUNT] = {
BFMT_SRGB(L8, R8),
BFMT_SRGB(L8A8, R8G8),
BFMT_SRGB(R8, R8),
BFMT_SRGB(R8G8, R8G8),
BFMT_SRGB(R8G8B8, R8G8B8),
BFMT_SRGB(B8G8R8, R8G8B8),
BFMT_SRGB(B8G8R8A8, R8G8B8A8),
BFMT_SRGB(B8G8R8X8, R8G8B8A8),
BFMT_SRGB(A8R8G8B8, R8G8B8A8),
BFMT_SRGB(X8R8G8B8, R8G8B8A8),
BFMT_SRGB(A8B8G8R8, R8G8B8A8),
BFMT_SRGB(X8B8G8R8, R8G8B8A8),
BFMT_SRGB(R8G8B8X8, R8G8B8A8),
BFMT_SRGB(R8G8B8A8, R8G8B8A8),
BFMT2(A8_UNORM, R8G8B8A8, R8, 0),
#if PAN_ARCH < 6
BFMT2(I8_UNORM, R8G8B8A8, R8, 0),
#endif
BFMT2(R5G6B5_UNORM, R5G6B5A0, R5G6B5, 0),
BFMT2(B5G6R5_UNORM, R5G6B5A0, R5G6B5, 0),
BFMT(R4G4B4A4_UNORM, R4G4B4A4),
BFMT(B4G4R4A4_UNORM, R4G4B4A4),
BFMT(A4R4G4B4_UNORM, R4G4B4A4),
BFMT(A4B4G4R4_UNORM, R4G4B4A4),
BFMT(R10G10B10A2_UNORM, R10G10B10A2),
BFMT(B10G10R10A2_UNORM, R10G10B10A2),
BFMT(R10G10B10X2_UNORM, R10G10B10A2),
BFMT(B10G10R10X2_UNORM, R10G10B10A2),
BFMT(B5G5R5A1_UNORM, R5G5B5A1),
BFMT(R5G5B5A1_UNORM, R5G5B5A1),
BFMT(B5G5R5X1_UNORM, R5G5B5A1),
#if PAN_ARCH >= 9
BFMT_FLOAT(R16_FLOAT, R16F),
BFMT_FLOAT(R16G16_FLOAT, RG16F),
BFMT_FLOAT(R16G16B16A16_FLOAT, RGBA16F),
BFMT_FLOAT(R11G11B10_FLOAT, RG11F_B10F),
#endif
};
/* Convenience */
#define _V PAN_BIND_VERTEX_BUFFER
#define _T PAN_BIND_SAMPLER_VIEW
#define _R PAN_BIND_RENDER_TARGET
#define _Z PAN_BIND_DEPTH_STENCIL
#define _I PAN_BIND_STORAGE_IMAGE
#define _B PAN_BIND_TEXEL_BUFFER
#define FLAGS_V_____ (_V)
#define FLAGS__T____ (_T)
#define FLAGS_VTR___ (_V | _T | _R)
#define FLAGS_VTR_IB (_V | _T | _R | _I | _B)
#define FLAGS_VT____ (_V | _T)
#define FLAGS_VT__IB (_V | _T | _I | _B)
#define FLAGS__T_Z__ (_T | _Z)
#define FMT(pipe, mali, swizzle, srgb, flags) \
[PIPE_FORMAT_##pipe] = { \
.hw = MALI_PACK_FMT(mali, swizzle, srgb), \
.bind = FLAGS_##flags, \
}
#if PAN_ARCH >= 7
#define YUV_NO_SWAP (0)
#define YUV_SWAP (1)
#if PAN_ARCH < 14
#define MALI_YUV_CR_SITING_CENTER_422 (MALI_YUV_CR_SITING_CENTER_Y)
#else
#define MALI_YUV_CR_SITING_CENTER_422 (MALI_YUV_CR_SITING_CENTER_X)
#endif
#define FMT_YUV(pipe, mali, swizzle, swap, siting, flags) \
[PIPE_FORMAT_##pipe] = { \
.hw = (MALI_YUV_SWIZZLE_##swizzle) | ((YUV_##swap) << 3) | \
((MALI_YUV_CR_SITING_##siting) << 9) | ((MALI_##mali) << 12), \
.bind = FLAGS_##flags, \
}
#endif
#if PAN_ARCH < 9
#define FMTC(pipe, texfeat, interchange, swizzle, srgb) \
[PIPE_FORMAT_##pipe] = { \
.hw = MALI_PACK_FMT(texfeat, swizzle, srgb), \
.bind = (PAN_BIND_SAMPLER_VIEW), \
.texfeat_bit = MALI_##texfeat, \
}
#else
/* Map to interchange format, as compression is specified in the plane
* descriptor on Valhall.
*/
#define FMTC(pipe, texfeat, interchange, swizzle, srgb) \
[PIPE_FORMAT_##pipe] = { \
.hw = MALI_PACK_FMT(interchange, swizzle, srgb), \
.bind = (PAN_BIND_SAMPLER_VIEW), \
.texfeat_bit = MALI_##texfeat, \
}
#endif
/* clang-format off */
const struct pan_format GENX(pan_pipe_format)[PIPE_FORMAT_COUNT] = {
FMT(NONE, CONSTANT, 0000, L, VTR_IB),
#if PAN_ARCH >= 14
/* Multiplane formats */
FMT_YUV(R8G8_R8B8_UNORM, Y8U8Y8V8_422, UVYA, NO_SWAP, CENTER_422, _T____),
FMT_YUV(G8R8_B8R8_UNORM, U8Y8V8Y8_422, UYVA, SWAP, CENTER_422, _T____),
FMT_YUV(R8B8_R8G8_UNORM, Y8U8Y8V8_422, VYUA, NO_SWAP, CENTER_422, _T____),
FMT_YUV(B8R8_G8R8_UNORM, U8Y8V8Y8_422, VUYA, SWAP, CENTER_422, _T____),
FMT_YUV(R8_G8B8_420_UNORM, Y8U8V8_420, YUVA, NO_SWAP, CENTER, _T____),
FMT_YUV(R8_B8G8_420_UNORM, Y8U8V8_420, YVUA, NO_SWAP, CENTER, _T____),
FMT_YUV(R8_G8_B8_420_UNORM, Y8U8V8_420, YUVA, NO_SWAP, CENTER, _T____),
FMT_YUV(R8_B8_G8_420_UNORM, Y8U8V8_420, YVUA, NO_SWAP, CENTER, _T____),
FMT_YUV(R8_G8B8_422_UNORM, Y8U8Y8V8_422, YUVA, NO_SWAP, CENTER_422, _T____),
FMT_YUV(R8_B8G8_422_UNORM, U8Y8V8Y8_422, YVUA, NO_SWAP, CENTER_422, _T____),
FMT_YUV(R10_G10B10_420_UNORM, YUYAAYVYAA_420, YUVA, NO_SWAP, CENTER, _T____),
FMT_YUV(R10_G10B10_422_UNORM, Y10X6U10X6Y10X6V10X6_422, YUVA, NO_SWAP, CENTER_422, _T____),
/* special internal formats */
FMT_YUV(R8G8B8_420_UNORM_PACKED, Y8U8V8_420, YUVA, NO_SWAP, CENTER, _T____),
FMT_YUV(R10G10B10_420_UNORM_PACKED, Y10U10V10_420, YUVA, NO_SWAP, CENTER, _T____),
FMT_YUV(X6R10X6G10_X6R10X6B10_422_UNORM, Y10X6U10X6Y10X6V10X6_422, UVYA, NO_SWAP, CENTER_422, _T____),
#elif PAN_ARCH >= 7
/* Multiplane formats */
FMT_YUV(R8G8_R8B8_UNORM, YUYV8, UVYA, NO_SWAP, CENTER_422, _T____),
FMT_YUV(G8R8_B8R8_UNORM, VYUY8, UYVA, SWAP, CENTER_422, _T____),
FMT_YUV(R8B8_R8G8_UNORM, YUYV8, VYUA, NO_SWAP, CENTER_422, _T____),
FMT_YUV(B8R8_G8R8_UNORM, VYUY8, VUYA, SWAP, CENTER_422, _T____),
FMT_YUV(R8_G8B8_420_UNORM, Y8_UV8_420, YUVA, NO_SWAP, CENTER, _T____),
FMT_YUV(R8_B8G8_420_UNORM, Y8_UV8_420, YVUA, NO_SWAP, CENTER, _T____),
FMT_YUV(R8_G8_B8_420_UNORM, Y8_U8_V8_420, YUVA, NO_SWAP, CENTER, _T____),
FMT_YUV(R8_B8_G8_420_UNORM, Y8_U8_V8_420, YVUA, NO_SWAP, CENTER, _T____),
FMT_YUV(R8_G8B8_422_UNORM, Y8_UV8_422, YUVA, NO_SWAP, CENTER_422, _T____),
FMT_YUV(R8_B8G8_422_UNORM, Y8_UV8_422, YVUA, NO_SWAP, CENTER_422, _T____),
FMT_YUV(R10_G10B10_420_UNORM, Y10_UV10_420, YUVA, NO_SWAP, CENTER, _T____),
FMT_YUV(R10_G10B10_422_UNORM, Y10_UV10_422, YUVA, NO_SWAP, CENTER_422, _T____),
/* special internal formats */
FMT_YUV(R8G8B8_420_UNORM_PACKED, Y8_UV8_420, YUVA, NO_SWAP, CENTER, _T____),
FMT_YUV(R10G10B10_420_UNORM_PACKED, Y10_UV10_420, YUVA, NO_SWAP, CENTER, _T____),
FMT_YUV(X6R10X6G10_X6R10X6B10_422_UNORM, YUYV10, UVYA, NO_SWAP, CENTER_422, _T____),
#endif
FMTC(ETC1_RGB8, ETC2_RGB8, RGBA8_UNORM, RGB1, L),
FMTC(ETC2_RGB8, ETC2_RGB8, RGBA8_UNORM, RGB1, L),
FMTC(ETC2_SRGB8, ETC2_RGB8, RGBA8_UNORM, RGB1, S),
FMTC(ETC2_R11_UNORM, ETC2_R11_UNORM, R16_UNORM, R001, L),
FMTC(ETC2_RGBA8, ETC2_RGBA8, RGBA8_UNORM, RGBA, L),
FMTC(ETC2_SRGBA8, ETC2_RGBA8, RGBA8_UNORM, RGBA, S),
FMTC(ETC2_RG11_UNORM, ETC2_RG11_UNORM, RG16_UNORM, RG01, L),
FMTC(ETC2_R11_SNORM, ETC2_R11_SNORM, R16_SNORM, R001, L),
FMTC(ETC2_RG11_SNORM, ETC2_RG11_SNORM, RG16_SNORM, RG01, L),
FMTC(ETC2_RGB8A1, ETC2_RGB8A1, RGBA8_UNORM, RGBA, L),
FMTC(ETC2_SRGB8A1, ETC2_RGB8A1, RGBA8_UNORM, RGBA, S),
FMTC(DXT1_RGB, BC1_UNORM, RGBA8_UNORM, RGB1, L),
FMTC(DXT1_RGBA, BC1_UNORM, RGBA8_UNORM, RGBA, L),
FMTC(DXT1_SRGB, BC1_UNORM, RGBA8_UNORM, RGB1, S),
FMTC(DXT1_SRGBA, BC1_UNORM, RGBA8_UNORM, RGBA, S),
FMTC(DXT3_RGBA, BC2_UNORM, RGBA8_UNORM, RGBA, L),
FMTC(DXT3_SRGBA, BC2_UNORM, RGBA8_UNORM, RGBA, S),
FMTC(DXT5_RGBA, BC3_UNORM, RGBA8_UNORM, RGBA, L),
FMTC(DXT5_SRGBA, BC3_UNORM, RGBA8_UNORM, RGBA, S),
FMTC(RGTC1_UNORM, BC4_UNORM, R16_UNORM, R001, L),
FMTC(RGTC1_SNORM, BC4_SNORM, R16_SNORM, R001, L),
FMTC(RGTC2_UNORM, BC5_UNORM, RG16_UNORM, RG01, L),
FMTC(RGTC2_SNORM, BC5_SNORM, RG16_SNORM, RG01, L),
FMTC(BPTC_RGB_FLOAT, BC6H_SF16, RGBA16F, RGB1, L),
FMTC(BPTC_RGB_UFLOAT, BC6H_UF16, RGBA16F, RGB1, L),
FMTC(BPTC_RGBA_UNORM, BC7_UNORM, RGBA8_UNORM, RGBA, L),
FMTC(BPTC_SRGBA, BC7_UNORM, RGBA8_UNORM, RGBA, S),
/* If ASTC decode mode is set to RGBA8, the hardware format
* will be overriden to RGBA8_UNORM later on.
*/
FMTC(ASTC_4x4, ASTC_2D_LDR, RGBA16F, RGBA, L),
FMTC(ASTC_5x4, ASTC_2D_LDR, RGBA16F, RGBA, L),
FMTC(ASTC_5x5, ASTC_2D_LDR, RGBA16F, RGBA, L),
FMTC(ASTC_6x5, ASTC_2D_LDR, RGBA16F, RGBA, L),
FMTC(ASTC_6x6, ASTC_2D_LDR, RGBA16F, RGBA, L),
FMTC(ASTC_8x5, ASTC_2D_LDR, RGBA16F, RGBA, L),
FMTC(ASTC_8x6, ASTC_2D_LDR, RGBA16F, RGBA, L),
FMTC(ASTC_8x8, ASTC_2D_LDR, RGBA16F, RGBA, L),
FMTC(ASTC_10x5, ASTC_2D_LDR, RGBA16F, RGBA, L),
FMTC(ASTC_10x6, ASTC_2D_LDR, RGBA16F, RGBA, L),
FMTC(ASTC_10x8, ASTC_2D_LDR, RGBA16F, RGBA, L),
FMTC(ASTC_10x10, ASTC_2D_LDR, RGBA16F, RGBA, L),
FMTC(ASTC_12x10, ASTC_2D_LDR, RGBA16F, RGBA, L),
FMTC(ASTC_12x12, ASTC_2D_LDR, RGBA16F, RGBA, L),
FMTC(ASTC_3x3x3, ASTC_3D_LDR, RGBA16F, RGBA, L),
FMTC(ASTC_4x3x3, ASTC_3D_LDR, RGBA16F, RGBA, L),
FMTC(ASTC_4x4x3, ASTC_3D_LDR, RGBA16F, RGBA, L),
FMTC(ASTC_4x4x4, ASTC_3D_LDR, RGBA16F, RGBA, L),
FMTC(ASTC_5x4x4, ASTC_3D_LDR, RGBA16F, RGBA, L),
FMTC(ASTC_5x5x4, ASTC_3D_LDR, RGBA16F, RGBA, L),
FMTC(ASTC_5x5x5, ASTC_3D_LDR, RGBA16F, RGBA, L),
FMTC(ASTC_6x5x5, ASTC_3D_LDR, RGBA16F, RGBA, L),
FMTC(ASTC_6x6x5, ASTC_3D_LDR, RGBA16F, RGBA, L),
FMTC(ASTC_6x6x6, ASTC_3D_LDR, RGBA16F, RGBA, L),
/* By definition, sRGB formats are narrow */
FMTC(ASTC_4x4_SRGB, ASTC_2D_LDR, RGBA8_UNORM, RGBA, S),
FMTC(ASTC_5x4_SRGB, ASTC_2D_LDR, RGBA8_UNORM, RGBA, S),
FMTC(ASTC_5x5_SRGB, ASTC_2D_LDR, RGBA8_UNORM, RGBA, S),
FMTC(ASTC_6x5_SRGB, ASTC_2D_LDR, RGBA8_UNORM, RGBA, S),
FMTC(ASTC_6x6_SRGB, ASTC_2D_LDR, RGBA8_UNORM, RGBA, S),
FMTC(ASTC_8x5_SRGB, ASTC_2D_LDR, RGBA8_UNORM, RGBA, S),
FMTC(ASTC_8x6_SRGB, ASTC_2D_LDR, RGBA8_UNORM, RGBA, S),
FMTC(ASTC_8x8_SRGB, ASTC_2D_LDR, RGBA8_UNORM, RGBA, S),
FMTC(ASTC_10x5_SRGB, ASTC_2D_LDR, RGBA8_UNORM, RGBA, S),
FMTC(ASTC_10x6_SRGB, ASTC_2D_LDR, RGBA8_UNORM, RGBA, S),
FMTC(ASTC_10x8_SRGB, ASTC_2D_LDR, RGBA8_UNORM, RGBA, S),
FMTC(ASTC_10x10_SRGB, ASTC_2D_LDR, RGBA8_UNORM, RGBA, S),
FMTC(ASTC_12x10_SRGB, ASTC_2D_LDR, RGBA8_UNORM, RGBA, S),
FMTC(ASTC_12x12_SRGB, ASTC_2D_LDR, RGBA8_UNORM, RGBA, S),
FMTC(ASTC_3x3x3_SRGB, ASTC_3D_LDR, RGBA8_UNORM, RGBA, S),
FMTC(ASTC_4x3x3_SRGB, ASTC_3D_LDR, RGBA8_UNORM, RGBA, S),
FMTC(ASTC_4x4x3_SRGB, ASTC_3D_LDR, RGBA8_UNORM, RGBA, S),
FMTC(ASTC_4x4x4_SRGB, ASTC_3D_LDR, RGBA8_UNORM, RGBA, S),
FMTC(ASTC_5x4x4_SRGB, ASTC_3D_LDR, RGBA8_UNORM, RGBA, S),
FMTC(ASTC_5x5x4_SRGB, ASTC_3D_LDR, RGBA8_UNORM, RGBA, S),
FMTC(ASTC_5x5x5_SRGB, ASTC_3D_LDR, RGBA8_UNORM, RGBA, S),
FMTC(ASTC_6x5x5_SRGB, ASTC_3D_LDR, RGBA8_UNORM, RGBA, S),
FMTC(ASTC_6x6x5_SRGB, ASTC_3D_LDR, RGBA8_UNORM, RGBA, S),
FMTC(ASTC_6x6x6_SRGB, ASTC_3D_LDR, RGBA8_UNORM, RGBA, S),
FMTC(ASTC_4x4_FLOAT, ASTC_2D_HDR, RGBA16F, RGBA, L),
FMTC(ASTC_5x4_FLOAT, ASTC_2D_HDR, RGBA16F, RGBA, L),
FMTC(ASTC_5x5_FLOAT, ASTC_2D_HDR, RGBA16F, RGBA, L),
FMTC(ASTC_6x5_FLOAT, ASTC_2D_HDR, RGBA16F, RGBA, L),
FMTC(ASTC_6x6_FLOAT, ASTC_2D_HDR, RGBA16F, RGBA, L),
FMTC(ASTC_8x5_FLOAT, ASTC_2D_HDR, RGBA16F, RGBA, L),
FMTC(ASTC_8x6_FLOAT, ASTC_2D_HDR, RGBA16F, RGBA, L),
FMTC(ASTC_8x8_FLOAT, ASTC_2D_HDR, RGBA16F, RGBA, L),
FMTC(ASTC_10x5_FLOAT, ASTC_2D_HDR, RGBA16F, RGBA, L),
FMTC(ASTC_10x6_FLOAT, ASTC_2D_HDR, RGBA16F, RGBA, L),
FMTC(ASTC_10x8_FLOAT, ASTC_2D_HDR, RGBA16F, RGBA, L),
FMTC(ASTC_10x10_FLOAT, ASTC_2D_HDR, RGBA16F, RGBA, L),
FMTC(ASTC_12x10_FLOAT, ASTC_2D_HDR, RGBA16F, RGBA, L),
FMTC(ASTC_12x12_FLOAT, ASTC_2D_HDR, RGBA16F, RGBA, L),
FMT(R5G6B5_UNORM, RGB565, RGB1, L, VTR_IB),
FMT(B5G6R5_UNORM, RGB565, BGR1, L, VTR_IB),
FMT(R5G5B5X1_UNORM, RGB5_A1_UNORM, RGB1, L, VT__IB),
FMT(B5G5R5X1_UNORM, RGB5_A1_UNORM, BGR1, L, VT__IB),
FMT(R5G5B5A1_UNORM, RGB5_A1_UNORM, RGBA, L, VTR_IB),
FMT(B5G5R5A1_UNORM, RGB5_A1_UNORM, BGRA, L, VTR_IB),
FMT(R10G10B10X2_UNORM, RGB10_A2_UNORM, RGB1, L, VTR_IB),
FMT(B10G10R10X2_UNORM, RGB10_A2_UNORM, BGR1, L, VTR_IB),
FMT(R10G10B10A2_UNORM, RGB10_A2_UNORM, RGBA, L, VTR_IB),
FMT(B10G10R10A2_UNORM, RGB10_A2_UNORM, BGRA, L, VTR_IB),
#if PAN_ARCH <= 5
FMT(R10G10B10X2_SNORM, RGB10_A2_SNORM, RGB1, L, VT__IB),
FMT(R10G10B10A2_SNORM, RGB10_A2_SNORM, RGBA, L, VT__IB),
FMT(B10G10R10A2_SNORM, RGB10_A2_SNORM, BGRA, L, VT__IB),
FMT(R3G3B2_UNORM, RGB332_UNORM, RGB1, L, _T____),
#else
FMT(R10G10B10X2_SNORM, RGB10_A2_SNORM, RGB1, L, V_____),
FMT(R10G10B10A2_SNORM, RGB10_A2_SNORM, RGBA, L, V_____),
FMT(B10G10R10A2_SNORM, RGB10_A2_SNORM, BGRA, L, V_____),
#endif
FMT(R10G10B10A2_UINT, RGB10_A2UI, RGBA, L, VTR_IB),
FMT(B10G10R10A2_UINT, RGB10_A2UI, BGRA, L, VTR_IB),
FMT(R10G10B10A2_USCALED, RGB10_A2UI, RGBA, L, V_____),
FMT(B10G10R10A2_USCALED, RGB10_A2UI, BGRA, L, V_____),
FMT(R10G10B10A2_SINT, RGB10_A2I, RGBA, L, VTR___),
FMT(B10G10R10A2_SINT, RGB10_A2I, BGRA, L, VTR___),
FMT(R10G10B10A2_SSCALED, RGB10_A2I, RGBA, L, V_____),
FMT(B10G10R10A2_SSCALED, RGB10_A2I, BGRA, L, V_____),
FMT(R8_SSCALED, R8I, R001, L, V_____),
FMT(R8G8_SSCALED, RG8I, RG01, L, V_____),
FMT(R8G8B8_SSCALED, RGB8I, RGB1, L, V_____),
FMT(B8G8R8_SSCALED, RGB8I, BGR1, L, V_____),
FMT(R8G8B8A8_SSCALED, RGBA8I, RGBA, L, V_____),
FMT(B8G8R8A8_SSCALED, RGBA8I, BGRA, L, V_____),
FMT(A8B8G8R8_SSCALED, RGBA8I, ABGR, L, V_____),
FMT(R8_USCALED, R8UI, R001, L, V_____),
FMT(R8G8_USCALED, RG8UI, RG01, L, V_____),
FMT(R8G8B8_USCALED, RGB8UI, RGB1, L, V_____),
FMT(B8G8R8_USCALED, RGB8UI, BGR1, L, V_____),
FMT(R8G8B8A8_USCALED, RGBA8UI, RGBA, L, V_____),
FMT(B8G8R8A8_USCALED, RGBA8UI, BGRA, L, V_____),
FMT(A8B8G8R8_USCALED, RGBA8UI, ABGR, L, V_____),
FMT(R16_USCALED, R16UI, R001, L, V_____),
FMT(R16G16_USCALED, RG16UI, RG01, L, V_____),
FMT(R16G16B16A16_USCALED, RGBA16UI, RGBA, L, V_____),
FMT(R16_SSCALED, R16I, R001, L, V_____),
FMT(R16G16_SSCALED, RG16I, RG01, L, V_____),
FMT(R16G16B16A16_SSCALED, RGBA16I, RGBA, L, V_____),
FMT(R32_USCALED, R32UI, R001, L, V_____),
FMT(R32G32_USCALED, RG32UI, RG01, L, V_____),
FMT(R32G32B32_USCALED, RGB32UI, RGB1, L, V_____),
FMT(R32G32B32A32_USCALED, RGBA32UI, RGBA, L, V_____),
FMT(R32_SSCALED, R32I, R001, L, V_____),
FMT(R32G32_SSCALED, RG32I, RG01, L, V_____),
FMT(R32G32B32_SSCALED, RGB32I, RGB1, L, V_____),
FMT(R32G32B32A32_SSCALED, RGBA32I, RGBA, L, V_____),
FMT(R32_FIXED, R32_FIXED, R001, L, V_____),
FMT(R32G32_FIXED, RG32_FIXED, RG01, L, V_____),
FMT(R32G32B32_FIXED, RGB32_FIXED, RGB1, L, V_____),
FMT(R32G32B32A32_FIXED, RGBA32_FIXED, RGBA, L, V_____),
FMT(R11G11B10_FLOAT, R11F_G11F_B10F, RGB1, L, VTR_IB),
#if PAN_ARCH < 7
FMT(R9G9B9E5_FLOAT, R9F_G9F_B9F_E5F, RGB1, L, _T____),
#else
FMT(R9G9B9E5_FLOAT, R9F_G9F_B9F_E5F, RGB1, L, VTR_IB),
#endif
#if PAN_ARCH >= 6
/* SNORM is renderable on Bifrost (with blend shaders) */
FMT(R8_SNORM, R8_SNORM, R001, L, VTR_IB),
FMT(R16_SNORM, R16_SNORM, R001, L, VTR_IB),
FMT(R8G8_SNORM, RG8_SNORM, RG01, L, VTR_IB),
FMT(R16G16_SNORM, RG16_SNORM, RG01, L, VTR_IB),
FMT(R8G8B8_SNORM, RGB8_SNORM, RGB1, L, VTR_IB),
FMT(R8G8B8A8_SNORM, RGBA8_SNORM, RGBA, L, VTR_IB),
FMT(B8G8R8A8_SNORM, RGBA8_SNORM, BGRA, L, VTR_IB),
FMT(R16G16B16A16_SNORM, RGBA16_SNORM, RGBA, L, VTR_IB),
#else
/* So far we haven't needed SNORM rendering on Midgard */
FMT(R8_SNORM, R8_SNORM, R001, L, VT__IB),
FMT(R16_SNORM, R16_SNORM, R001, L, VT__IB),
FMT(R8G8_SNORM, RG8_SNORM, RG01, L, VT__IB),
FMT(R16G16_SNORM, RG16_SNORM, RG01, L, VT__IB),
FMT(R8G8B8_SNORM, RGB8_SNORM, RGB1, L, VT__IB),
FMT(R8G8B8A8_SNORM, RGBA8_SNORM, RGBA, L, VT__IB),
FMT(B8G8R8A8_SNORM, RGBA8_SNORM, BGRA, L, VT__IB),
FMT(R16G16B16A16_SNORM, RGBA16_SNORM, RGBA, L, VT__IB),
#endif
FMT(I8_SINT, R8I, RRRR, L, VTR_IB),
FMT(L8_SINT, R8I, RRR1, L, VTR_IB),
FMT(I8_UINT, R8UI, RRRR, L, VTR_IB),
FMT(L8_UINT, R8UI, RRR1, L, VTR_IB),
FMT(I16_SINT, R16I, RRRR, L, VTR_IB),
FMT(L16_SINT, R16I, RRR1, L, VTR_IB),
FMT(I16_UINT, R16UI, RRRR, L, VTR_IB),
FMT(L16_UINT, R16UI, RRR1, L, VTR_IB),
FMT(I32_SINT, R32I, RRRR, L, VTR_IB),
FMT(L32_SINT, R32I, RRR1, L, VTR_IB),
FMT(I32_UINT, R32UI, RRRR, L, VTR_IB),
FMT(L32_UINT, R32UI, RRR1, L, VTR_IB),
FMT(B8G8R8_UINT, RGB8UI, BGR1, L, V_____),
FMT(B8G8R8_SINT, RGB8I, BGR1, L, V_____),
FMT(B8G8R8A8_UINT, RGBA8UI, BGRA, L, VTR_IB),
FMT(B8G8R8A8_SINT, RGBA8I, BGRA, L, VTR_IB),
FMT(A8R8G8B8_UINT, RGBA8UI, GBAR, L, VTR_IB),
FMT(A8B8G8R8_UINT, RGBA8UI, ABGR, L, VTR_IB),
FMT(R8_UINT, R8UI, R001, L, VTR_IB),
FMT(R16_UINT, R16UI, R001, L, VTR_IB),
FMT(R32_UINT, R32UI, R001, L, VTR_IB),
FMT(R8G8_UINT, RG8UI, RG01, L, VTR_IB),
FMT(R16G16_UINT, RG16UI, RG01, L, VTR_IB),
FMT(R32G32_UINT, RG32UI, RG01, L, VTR_IB),
FMT(R8G8B8_UINT, RGB8UI, RGB1, L, V_____),
FMT(R32G32B32_UINT, RGB32UI, RGB1, L, VTR_IB),
FMT(R8G8B8A8_UINT, RGBA8UI, RGBA, L, VTR_IB),
FMT(R16G16B16A16_UINT, RGBA16UI, RGBA, L, VTR_IB),
FMT(R32G32B32A32_UINT, RGBA32UI, RGBA, L, VTR_IB),
FMT(R32_FLOAT, R32F, R001, L, VTR_IB),
FMT(R32G32_FLOAT, RG32F, RG01, L, VTR_IB),
FMT(R32G32B32_FLOAT, RGB32F, RGB1, L, VTR_IB),
FMT(R32G32B32A32_FLOAT, RGBA32F, RGBA, L, VTR_IB),
FMT(R8_UNORM, R8_UNORM, R001, L, VTR_IB),
FMT(R16_UNORM, R16_UNORM, R001, L, VTR_IB),
FMT(R8G8_UNORM, RG8_UNORM, RG01, L, VTR_IB),
FMT(R16G16_UNORM, RG16_UNORM, RG01, L, VTR_IB),
FMT(R8G8B8_UNORM, RGB8_UNORM, RGB1, L, VTR_IB),
FMT(B8G8R8_UNORM, RGB8_UNORM, BGR1, L, VTR_IB),
/* 32-bit NORM is not texturable in v7 onwards. It's renderable
* everywhere, but rendering without texturing is not useful.
*/
#if PAN_ARCH <= 6
FMT(R32_UNORM, R32_UNORM, R001, L, VTR_IB),
FMT(R32G32_UNORM, RG32_UNORM, RG01, L, VTR_IB),
FMT(R32G32B32_UNORM, RGB32_UNORM, RGB1, L, VT__IB),
FMT(R32G32B32A32_UNORM, RGBA32_UNORM, RGBA, L, VTR_IB),
FMT(R32_SNORM, R32_SNORM, R001, L, VT__IB),
FMT(R32G32_SNORM, RG32_SNORM, RG01, L, VT__IB),
FMT(R32G32B32_SNORM, RGB32_SNORM, RGB1, L, VT__IB),
FMT(R32G32B32A32_SNORM, RGBA32_SNORM, RGBA, L, VT__IB),
#else
FMT(R32_UNORM, R32_UNORM, R001, L, V_____),
FMT(R32G32_UNORM, RG32_UNORM, RG01, L, V_____),
FMT(R32G32B32_UNORM, RGB32_UNORM, RGB1, L, V_____),
FMT(R32G32B32A32_UNORM, RGBA32_UNORM, RGBA, L, V_____),
FMT(R32_SNORM, R32_SNORM, R001, L, V_____),
FMT(R32G32_SNORM, RG32_SNORM, RG01, L, V_____),
FMT(R32G32B32_SNORM, RGB32_SNORM, RGB1, L, V_____),
FMT(R32G32B32A32_SNORM, RGBA32_SNORM, RGBA, L, V_____),
#endif
/* Don't allow render/texture for 48-bit */
FMT(R16G16B16_UNORM, RGB16_UNORM, RGB1, L, V_____),
FMT(R16G16B16_SINT, RGB16I, RGB1, L, V_____),
FMT(R16G16B16_FLOAT, RGB16F, RGB1, L, V_____),
FMT(R16G16B16_USCALED, RGB16UI, RGB1, L, V_____),
FMT(R16G16B16_SSCALED, RGB16I, RGB1, L, V_____),
FMT(R16G16B16_SNORM, RGB16_SNORM, RGB1, L, V_____),
FMT(R16G16B16_UINT, RGB16UI, RGB1, L, V_____),
FMT(R4G4B4A4_UNORM, RGBA4_UNORM, RGBA, L, VTR___),
FMT(B4G4R4A4_UNORM, RGBA4_UNORM, BGRA, L, VTR___),
FMT(A4R4G4B4_UNORM, RGBA4_UNORM, ARGB, L, VTR___),
FMT(A4B4G4R4_UNORM, RGBA4_UNORM, ABGR, L, VTR___),
FMT(R16G16B16A16_UNORM, RGBA16_UNORM, RGBA, L, VTR_IB),
#if PAN_ARCH >= 11
FMT(X6R10X6G10X6B10X6A10_UNORM, R10X6G10X6B10X6A10X6_UNORM, RGBA, L, _T____),
#endif
FMT(B8G8R8A8_UNORM, RGBA8_UNORM, BGRA, L, VTR_IB),
FMT(B8G8R8X8_UNORM, RGBA8_UNORM, BGR1, L, VTR_IB),
FMT(A8R8G8B8_UNORM, RGBA8_UNORM, GBAR, L, VTR_IB),
FMT(X8R8G8B8_UNORM, RGBA8_UNORM, GBA1, L, VTR_IB),
FMT(A8B8G8R8_UNORM, RGBA8_UNORM, ABGR, L, VTR_IB),
FMT(X8B8G8R8_UNORM, RGBA8_UNORM, ABG1, L, VTR_IB),
FMT(R8G8B8X8_UNORM, RGBA8_UNORM, RGB1, L, VTR_IB),
FMT(R8G8B8A8_UNORM, RGBA8_UNORM, RGBA, L, VTR_IB),
FMT(R8G8B8X8_SNORM, RGBA8_SNORM, RGB1, L, VT__IB),
FMT(R8G8B8X8_SRGB, RGBA8_UNORM, RGB1, S, VTR_IB),
FMT(R8G8B8X8_UINT, RGBA8UI, RGB1, L, VTR_IB),
FMT(R8G8B8X8_SINT, RGBA8I, RGB1, L, VTR_IB),
FMT(L8_UNORM, R8_UNORM, RRR1, L, VTR_IB),
FMT(I8_UNORM, R8_UNORM, RRRR, L, VTR_IB),
FMT(L16_UNORM, R16_UNORM, RRR1, L, VT__IB),
FMT(I16_UNORM, R16_UNORM, RRRR, L, VT__IB),
FMT(L8_SNORM, R8_SNORM, RRR1, L, VT__IB),
FMT(I8_SNORM, R8_SNORM, RRRR, L, VT__IB),
FMT(L16_SNORM, R16_SNORM, RRR1, L, VT__IB),
FMT(I16_SNORM, R16_SNORM, RRRR, L, VT__IB),
FMT(L16_FLOAT, R16F, RRR1, L, VTR_IB),
FMT(I16_FLOAT, R16F, RRRR, L, VTR_IB),
FMT(L8_SRGB, R8_UNORM, RRR1, S, VTR_IB),
FMT(R8_SRGB, R8_UNORM, R001, S, VTR_IB),
FMT(R8G8_SRGB, RG8_UNORM, RG01, S, VTR_IB),
FMT(R8G8B8_SRGB, RGB8_UNORM, RGB1, S, VTR_IB),
FMT(B8G8R8_SRGB, RGB8_UNORM, BGR1, S, VTR_IB),
FMT(R8G8B8A8_SRGB, RGBA8_UNORM, RGBA, S, VTR_IB),
FMT(A8B8G8R8_SRGB, RGBA8_UNORM, ABGR, S, VTR_IB),
FMT(X8B8G8R8_SRGB, RGBA8_UNORM, ABG1, S, VTR_IB),
FMT(B8G8R8A8_SRGB, RGBA8_UNORM, BGRA, S, VTR_IB),
FMT(B8G8R8X8_SRGB, RGBA8_UNORM, BGR1, S, VTR_IB),
FMT(A8R8G8B8_SRGB, RGBA8_UNORM, GBAR, S, VTR_IB),
FMT(X8R8G8B8_SRGB, RGBA8_UNORM, GBA1, S, VTR_IB),
FMT(R8_SINT, R8I, R001, L, VTR_IB),
FMT(R16_SINT, R16I, R001, L, VTR_IB),
FMT(R32_SINT, R32I, R001, L, VTR_IB),
FMT(R16_FLOAT, R16F, R001, L, VTR_IB),
FMT(R8G8_SINT, RG8I, RG01, L, VTR_IB),
FMT(R16G16_SINT, RG16I, RG01, L, VTR_IB),
FMT(R32G32_SINT, RG32I, RG01, L, VTR_IB),
FMT(R16G16_FLOAT, RG16F, RG01, L, VTR_IB),
FMT(R8G8B8_SINT, RGB8I, RGB1, L, V_____),
FMT(R32G32B32_SINT, RGB32I, RGB1, L, VTR_IB),
FMT(R8G8B8A8_SINT, RGBA8I, RGBA, L, VTR_IB),
FMT(R16G16B16A16_SINT, RGBA16I, RGBA, L, VTR_IB),
FMT(R32G32B32A32_SINT, RGBA32I, RGBA, L, VTR_IB),
FMT(R16G16B16A16_FLOAT, RGBA16F, RGBA, L, VTR_IB),
FMT(R16G16B16X16_UNORM, RGBA16_UNORM, RGB1, L, VTR_IB),
FMT(R16G16B16X16_SNORM, RGBA16_SNORM, RGB1, L, VT__IB),
FMT(R16G16B16X16_FLOAT, RGBA16F, RGB1, L, VTR_IB),
FMT(R16G16B16X16_UINT, RGBA16UI, RGB1, L, VTR_IB),
FMT(R16G16B16X16_SINT, RGBA16I, RGB1, L, VTR_IB),
FMT(R32G32B32X32_FLOAT, RGBA32F, RGB1, L, VTR_IB),
FMT(R32G32B32X32_UINT, RGBA32UI, RGB1, L, VTR_IB),
FMT(R32G32B32X32_SINT, RGBA32I, RGB1, L, VTR_IB),
#if PAN_ARCH <= 6
FMT(Z16_UNORM, R16_UNORM, RRRR, L, _T_Z__),
FMT(Z24_UNORM_S8_UINT, Z24X8_UNORM, RRRR, L, _T_Z__),
FMT(Z24X8_UNORM, Z24X8_UNORM, RRRR, L, _T_Z__),
FMT(Z32_FLOAT, R32F, RRRR, L, _T_Z__),
FMT(Z32_FLOAT_S8X24_UINT, RG32F, RRRR, L, _T_Z__),
FMT(X32_S8X24_UINT, X32_S8X24, GGGG, L, _T_Z__),
FMT(X24S8_UINT, RGBA8UI, AAAA, L, _T_Z__),
FMT(S8_UINT, R8UI, RRRR, L, _T_Z__),
FMT(A8_UNORM, R8_UNORM, 000R, L, VTR___),
FMT(L8A8_UNORM, RG8_UNORM, RRRG, L, VTR_IB),
FMT(L8A8_SRGB, RG8_UNORM, RRRG, S, VTR_IB),
/* These formats were removed in v7 */
FMT(A8_SNORM, R8_SNORM, 000R, L, VT__IB),
FMT(A8_SINT, R8I, 000R, L, VTR_IB),
FMT(A8_UINT, R8UI, 000R, L, VTR_IB),
FMT(A16_SINT, R16I, 000R, L, VTR_IB),
FMT(A16_UINT, R16UI, 000R, L, VTR_IB),
FMT(A32_SINT, R32I, 000R, L, VTR_IB),
FMT(A32_UINT, R32UI, 000R, L, VTR_IB),
FMT(A16_UNORM, R16_UNORM, 000R, L, VT__IB),
FMT(A16_SNORM, R16_SNORM, 000R, L, VT__IB),
FMT(A16_FLOAT, R16F, 000R, L, VTR_IB),
#else
FMT(Z16_UNORM, Z16_UNORM, RGBA, L, _T_Z__),
FMT(Z24_UNORM_S8_UINT, Z24X8_UNORM, RGBA, L, _T_Z__),
FMT(Z24X8_UNORM, Z24X8_UNORM, RGBA, L, _T_Z__),
FMT(Z32_FLOAT, Z32, RGBA, L, _T_Z__),
#if PAN_ARCH >= 9
/* Specify interchange formats, the actual format for depth/stencil is
* determined by the plane descriptor on Valhall.
*
* On Valhall, S8 logically acts like "X8S8", so "S8 RGBA" is logically
* "0s00" and "S8 GRBA" is logically "s000". For Bifrost compatibility
* we want stencil in the red channel, so we use the GRBA swizzles.
*/
FMT(Z32_FLOAT_S8X24_UINT, R32F, GRBA, L, _T_Z__),
FMT(Z24_UNORM_PACKED, Z24X8_UNORM, RGBA, L, _T_Z__),
FMT(X32_S8X24_UINT, S8, GRBA, L, _T_Z__),
FMT(X24S8_UINT, S8, GRBA, L, _T_Z__),
FMT(S8_UINT, S8, GRBA, L, _T_Z__),
/* similarly, the interchange format is RGBA8, but we only
actually store 1 component in memory here */
FMT(A8_UNORM, RGBA8_UNORM, 000A, L, VTR___),
#else
/* Specify real formats on Bifrost */
FMT(Z32_FLOAT_S8X24_UINT, Z32_X32, RGBA, L, _T_Z__),
FMT(X32_S8X24_UINT, X32_S8X24, GRBA, L, _T_Z__),
FMT(X24S8_UINT, X24S8, GRBA, L, _T_Z__),
FMT(S8_UINT, S8, GRBA, L, _T_Z__),
/* Obsolete formats removed in Valhall */
FMT(A8_UNORM, A8_UNORM, 000A, L, VTR___),
FMT(L8A8_UNORM, R8A8_UNORM, RRRA, L, VTR_IB),
FMT(L8A8_SRGB, R8A8_UNORM, RRRA, S, VTR_IB),
#endif
#endif
};
/* clang-format on */
/* This is only needed on Midgard. It's the same on both v4 and v5, so only
* compile once to avoid the GenXML dependency for calls.
*/
#if PAN_ARCH == 5
uint8_t
pan_raw_format_mask_midgard(enum pipe_format *formats)
{
uint8_t out = 0;
for (unsigned i = 0; i < 8; i++) {
enum pipe_format fmt = formats[i];
unsigned wb_fmt = pan_blendable_formats_v6[fmt].writeback;
if (wb_fmt < MALI_COLOR_FORMAT_R8)
out |= BITFIELD_BIT(i);
}
return out;
}
#endif
#if PAN_ARCH == 7 || PAN_ARCH >= 10
/*
* Decompose a component ordering swizzle into a component ordering (applied
* first) and a swizzle (applied second). The output ordering "pre" is allowed
* with compression and the swizzle "post" is a bijection.
*
* These properties allow any component ordering to be used with compression, by
* using the output "pre" ordering, composing the API swizzle with the "post"
* ordering, and applying the inverse of the "post" ordering to the border
* colour to undo what we compose into the API swizzle.
*
* Note that "post" is a swizzle, not a component ordering, which means it is
* inverted from the ordering. E.g. ARGB ordering uses a GBAR (YZWX) swizzle.
*/
struct pan_decomposed_swizzle
GENX(pan_decompose_swizzle)(enum mali_rgb_component_order order)
{
#define CASE(case_, pre_, R_, G_, B_, A_) \
case MALI_RGB_COMPONENT_ORDER_##case_: \
return (struct pan_decomposed_swizzle){ \
MALI_RGB_COMPONENT_ORDER_##pre_, \
{ \
PIPE_SWIZZLE_##R_, \
PIPE_SWIZZLE_##G_, \
PIPE_SWIZZLE_##B_, \
PIPE_SWIZZLE_##A_, \
}, \
};
switch (order) {
CASE(RGBA, RGBA, X, Y, Z, W);
CASE(GRBA, RGBA, Y, X, Z, W);
CASE(BGRA, RGBA, Z, Y, X, W);
CASE(ARGB, RGBA, Y, Z, W, X);
CASE(AGRB, RGBA, Z, Y, W, X);
CASE(ABGR, RGBA, W, Z, Y, X);
CASE(RGB1, RGB1, X, Y, Z, W);
CASE(GRB1, RGB1, Y, X, Z, W);
CASE(BGR1, RGB1, Z, Y, X, W);
CASE(1RGB, RGB1, Y, Z, W, X);
CASE(1GRB, RGB1, Z, Y, W, X);
CASE(1BGR, RGB1, W, Z, Y, X);
CASE(RRRR, RRRR, X, Y, Z, W);
CASE(RRR1, RRR1, X, Y, Z, W);
CASE(RRRA, RRRA, X, Y, Z, W);
CASE(000A, 000A, X, Y, Z, W);
CASE(0001, 0001, X, Y, Z, W);
CASE(0000, 0000, X, Y, Z, W);
default:
UNREACHABLE("Invalid case for texturing");
}
#undef CASE
}
#endif
+370
View File
@@ -0,0 +1,370 @@
/*
* Copyright (C) 2008 VMware, Inc.
* Copyright (C) 2014 Broadcom
* Copyright (C) 2018-2019 Alyssa Rosenzweig
* Copyright (C) 2019-2020 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#ifndef __PAN_FORMAT_H
#define __PAN_FORMAT_H
#include "genxml/gen_macros.h"
#include "util/format/u_format.h"
#include "drm-uapi/drm_fourcc.h"
/*
* List of supported modifiers, in descending order of preference. AFBC is
* faster than u-interleaved tiling which is faster than linear. Within AFBC,
* enabling the YUV-like transform is typically a win where possible.
* AFRC is only used if explicitly asked for (only for RGB formats).
* Similarly MTK 16L32 is only used if explicitly asked for.
*/
#define PAN_SUPPORTED_MODIFIERS(__name) \
static const uint64_t __name[] = { \
DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_32x8 | \
AFBC_FORMAT_MOD_SPARSE), \
DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_32x8 | \
AFBC_FORMAT_MOD_SPARSE | AFBC_FORMAT_MOD_SPLIT), \
DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_32x8 | \
AFBC_FORMAT_MOD_SPARSE | AFBC_FORMAT_MOD_SPLIT | \
AFBC_FORMAT_MOD_YTR), \
\
DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 | \
AFBC_FORMAT_MOD_TILED | AFBC_FORMAT_MOD_SC | \
AFBC_FORMAT_MOD_SPARSE | AFBC_FORMAT_MOD_YTR), \
\
DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 | \
AFBC_FORMAT_MOD_TILED | AFBC_FORMAT_MOD_SC | \
AFBC_FORMAT_MOD_SPARSE), \
\
DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 | \
AFBC_FORMAT_MOD_SPARSE | AFBC_FORMAT_MOD_YTR), \
\
DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 | \
AFBC_FORMAT_MOD_SPARSE), \
\
DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 | \
AFBC_FORMAT_MOD_SPARSE | AFBC_FORMAT_MOD_SPLIT), \
\
DRM_FORMAT_MOD_ARM_INTERLEAVED_64K, \
DRM_FORMAT_MOD_ARM_16X16_BLOCK_U_INTERLEAVED, \
DRM_FORMAT_MOD_LINEAR, \
\
DRM_FORMAT_MOD_ARM_AFRC( \
AFRC_FORMAT_MOD_CU_SIZE_P0(AFRC_FORMAT_MOD_CU_SIZE_16)), \
DRM_FORMAT_MOD_ARM_AFRC( \
AFRC_FORMAT_MOD_CU_SIZE_P0(AFRC_FORMAT_MOD_CU_SIZE_24)), \
DRM_FORMAT_MOD_ARM_AFRC( \
AFRC_FORMAT_MOD_CU_SIZE_P0(AFRC_FORMAT_MOD_CU_SIZE_32)), \
DRM_FORMAT_MOD_ARM_AFRC( \
AFRC_FORMAT_MOD_CU_SIZE_P0(AFRC_FORMAT_MOD_CU_SIZE_16) | \
AFRC_FORMAT_MOD_LAYOUT_SCAN), \
DRM_FORMAT_MOD_ARM_AFRC( \
AFRC_FORMAT_MOD_CU_SIZE_P0(AFRC_FORMAT_MOD_CU_SIZE_24) | \
AFRC_FORMAT_MOD_LAYOUT_SCAN), \
DRM_FORMAT_MOD_ARM_AFRC( \
AFRC_FORMAT_MOD_CU_SIZE_P0(AFRC_FORMAT_MOD_CU_SIZE_32) | \
AFRC_FORMAT_MOD_LAYOUT_SCAN), \
}
/* DRM modifier helper */
#define drm_is_afbc(mod) \
((mod >> 52) == \
(DRM_FORMAT_MOD_ARM_TYPE_AFBC | (DRM_FORMAT_MOD_VENDOR_ARM << 4)))
#define drm_is_afrc(mod) \
((mod >> 52) == \
(DRM_FORMAT_MOD_ARM_TYPE_AFRC | (DRM_FORMAT_MOD_VENDOR_ARM << 4)))
static inline bool
pan_u_tiled_or_linear_supports_format(enum pipe_format format)
{
switch (format) {
case PIPE_FORMAT_R8G8B8_420_UNORM_PACKED:
case PIPE_FORMAT_R10G10B10_420_UNORM_PACKED:
return false;
default:
return true;
}
}
/* Formats */
static inline unsigned
pan_format_get_plane_blocksize(enum pipe_format format, unsigned plane_idx)
{
switch (format) {
case PIPE_FORMAT_R8_G8B8_420_UNORM:
case PIPE_FORMAT_R8_B8G8_420_UNORM:
case PIPE_FORMAT_R8_G8B8_422_UNORM:
case PIPE_FORMAT_R8_B8G8_422_UNORM:
return plane_idx ? 2 : 1;
case PIPE_FORMAT_R10_G10B10_420_UNORM:
case PIPE_FORMAT_R10_G10B10_422_UNORM:
return plane_idx ? 10 : 5;
case PIPE_FORMAT_R8_G8_B8_420_UNORM:
case PIPE_FORMAT_R8_B8_G8_420_UNORM:
return 1;
default:
assert(util_format_get_num_planes(format) == 1);
return util_format_get_blocksize(format);
}
}
static inline unsigned
pan_format_tib_size(enum pipe_format format, bool internal)
{
if (internal && !util_format_is_float(format)) {
/* Blendable UNORM and sRGB formats are always 32-bits in the tile
* buffer, extra bits are used as padding or to dither */
return 4;
} else {
/* Non-blendable and float formats are raw, rounded up to the nearest
* power-of-two size */
unsigned bytes = util_format_get_blocksize(format);
return util_next_power_of_two(bytes);
}
}
typedef uint32_t mali_pixel_format;
/* pan bind flags */
#define PAN_BIND_DEPTH_STENCIL BITFIELD_BIT(0)
#define PAN_BIND_RENDER_TARGET BITFIELD_BIT(1)
#define PAN_BIND_SAMPLER_VIEW BITFIELD_BIT(2)
#define PAN_BIND_VERTEX_BUFFER BITFIELD_BIT(3)
#define PAN_BIND_STORAGE_IMAGE BITFIELD_BIT(4)
#define PAN_BIND_TEXEL_BUFFER BITFIELD_BIT(5)
struct pan_format {
uint32_t hw : 21;
uint32_t texfeat_bit : 5;
uint32_t bind : 6;
};
struct pan_blendable_format {
/* enum mali_color_buffer_internal_format */ uint16_t internal;
/* enum mali_mfbd_color_format */ uint16_t writeback;
/* Indexed by the dithered? flag. So _PU first, then _AU */
mali_pixel_format bifrost[2];
};
#define pan_blendable_formats_v4 pan_blendable_formats_v5
extern const struct pan_blendable_format
pan_blendable_formats_v5[PIPE_FORMAT_COUNT];
extern const struct pan_blendable_format
pan_blendable_formats_v6[PIPE_FORMAT_COUNT];
extern const struct pan_blendable_format
pan_blendable_formats_v7[PIPE_FORMAT_COUNT];
extern const struct pan_blendable_format
pan_blendable_formats_v9[PIPE_FORMAT_COUNT];
extern const struct pan_blendable_format
pan_blendable_formats_v10[PIPE_FORMAT_COUNT];
extern const struct pan_blendable_format
pan_blendable_formats_v12[PIPE_FORMAT_COUNT];
extern const struct pan_blendable_format
pan_blendable_formats_v13[PIPE_FORMAT_COUNT];
extern const struct pan_blendable_format
pan_blendable_formats_v14[PIPE_FORMAT_COUNT];
uint8_t pan_raw_format_mask_midgard(enum pipe_format *formats);
static inline const struct pan_blendable_format *
pan_blendable_format_table(unsigned arch)
{
switch (arch) {
#define FMT_TABLE(x) case x: return pan_blendable_formats_v ## x
FMT_TABLE(4);
FMT_TABLE(5);
FMT_TABLE(6);
FMT_TABLE(7);
FMT_TABLE(9);
FMT_TABLE(10);
FMT_TABLE(12);
FMT_TABLE(13);
FMT_TABLE(14);
#undef FMT_TABLE
default:
assert(!"Unsupported architecture");
return NULL;
}
}
#define pan_pipe_format_v4 pan_pipe_format_v5
extern const struct pan_format pan_pipe_format_v5[PIPE_FORMAT_COUNT];
extern const struct pan_format pan_pipe_format_v6[PIPE_FORMAT_COUNT];
extern const struct pan_format pan_pipe_format_v7[PIPE_FORMAT_COUNT];
extern const struct pan_format pan_pipe_format_v9[PIPE_FORMAT_COUNT];
extern const struct pan_format pan_pipe_format_v10[PIPE_FORMAT_COUNT];
extern const struct pan_format pan_pipe_format_v12[PIPE_FORMAT_COUNT];
extern const struct pan_format pan_pipe_format_v13[PIPE_FORMAT_COUNT];
extern const struct pan_format pan_pipe_format_v14[PIPE_FORMAT_COUNT];
static inline const struct pan_format *
pan_format_table(unsigned arch)
{
switch (arch) {
#define FMT_TABLE(x) case x: return pan_pipe_format_v ## x
FMT_TABLE(4);
FMT_TABLE(5);
FMT_TABLE(6);
FMT_TABLE(7);
FMT_TABLE(9);
FMT_TABLE(10);
FMT_TABLE(12);
FMT_TABLE(13);
FMT_TABLE(14);
#undef FMT_TABLE
default:
assert(!"Unsupported architecture");
return NULL;
}
}
/* Helpers to construct swizzles */
#define PAN_V6_SWIZZLE(R, G, B, A) \
(((MALI_CHANNEL_##R) << 0) | ((MALI_CHANNEL_##G) << 3) | \
((MALI_CHANNEL_##B) << 6) | ((MALI_CHANNEL_##A) << 9))
static inline unsigned
pan_get_default_swizzle(unsigned components)
{
switch (components) {
case 1:
return PAN_V6_SWIZZLE(R, 0, 0, 1);
case 2:
return PAN_V6_SWIZZLE(R, G, 0, 1);
case 3:
return PAN_V6_SWIZZLE(R, G, B, 1);
case 4:
return PAN_V6_SWIZZLE(R, G, B, A);
default:
UNREACHABLE("Invalid number of components");
}
}
#if PAN_ARCH == 7 || PAN_ARCH >= 10
struct pan_decomposed_swizzle {
/* Component ordering to apply first */
enum mali_rgb_component_order pre;
/* Bijective swizzle applied after */
unsigned char post[4];
};
struct pan_decomposed_swizzle
GENX(pan_decompose_swizzle)(enum mali_rgb_component_order order);
#endif
#define MALI_SRGB_L (0)
#define MALI_SRGB_S (1)
#if PAN_ARCH <= 6
#define MALI_V6_0000 PAN_V6_SWIZZLE(0, 0, 0, 0)
#define MALI_V6_000R PAN_V6_SWIZZLE(0, 0, 0, R)
#define MALI_V6_0R00 PAN_V6_SWIZZLE(0, R, 0, 0)
#define MALI_V6_0A00 PAN_V6_SWIZZLE(0, A, 0, 0)
#define MALI_V6_AAAA PAN_V6_SWIZZLE(A, A, A, A)
#define MALI_V6_A001 PAN_V6_SWIZZLE(A, 0, 0, 1)
#define MALI_V6_ABG1 PAN_V6_SWIZZLE(A, B, G, 1)
#define MALI_V6_ABGR PAN_V6_SWIZZLE(A, B, G, R)
#define MALI_V6_ARGB PAN_V6_SWIZZLE(A, R, G, B)
#define MALI_V6_BGR1 PAN_V6_SWIZZLE(B, G, R, 1)
#define MALI_V6_BGRA PAN_V6_SWIZZLE(B, G, R, A)
#define MALI_V6_GBA1 PAN_V6_SWIZZLE(G, B, A, 1)
#define MALI_V6_GBAR PAN_V6_SWIZZLE(G, B, A, R)
#define MALI_V6_R000 PAN_V6_SWIZZLE(R, 0, 0, 0)
#define MALI_V6_R001 PAN_V6_SWIZZLE(R, 0, 0, 1)
#define MALI_V6_RG01 PAN_V6_SWIZZLE(R, G, 0, 1)
#define MALI_V6_RGB1 PAN_V6_SWIZZLE(R, G, B, 1)
#define MALI_V6_RGBA PAN_V6_SWIZZLE(R, G, B, A)
#define MALI_V6_RRR1 PAN_V6_SWIZZLE(R, R, R, 1)
#define MALI_V6_RRRG PAN_V6_SWIZZLE(R, R, R, G)
#define MALI_V6_RRRR PAN_V6_SWIZZLE(R, R, R, R)
#define MALI_V6_GGGG PAN_V6_SWIZZLE(G, G, G, G)
#define MALI_PACK_FMT(mali, swizzle, srgb) \
(MALI_V6_##swizzle) | ((MALI_##mali) << 12) | (((MALI_SRGB_##srgb)) << 20)
#else
#define MALI_RGB_COMPONENT_ORDER_R001 MALI_RGB_COMPONENT_ORDER_RGB1
#define MALI_RGB_COMPONENT_ORDER_RG01 MALI_RGB_COMPONENT_ORDER_RGB1
#define MALI_RGB_COMPONENT_ORDER_GBAR MALI_RGB_COMPONENT_ORDER_ARGB
#define MALI_RGB_COMPONENT_ORDER_GBA1 MALI_RGB_COMPONENT_ORDER_1RGB
#define MALI_RGB_COMPONENT_ORDER_ABG1 MALI_RGB_COMPONENT_ORDER_1BGR
#define MALI_PACK_FMT(mali, swizzle, srgb) \
(MALI_RGB_COMPONENT_ORDER_##swizzle) | ((MALI_##mali) << 12) | \
(((MALI_SRGB_##srgb)) << 20)
#endif
#define MALI_EXTRACT_INDEX(pixfmt) (((pixfmt) >> 12) & 0xFF)
static inline bool
pan_format_is_yuv(enum pipe_format f)
{
enum util_format_layout layout = util_format_description(f)->layout;
/* Mesa's subsampled RGB formats are considered YUV formats on Mali */
return layout == UTIL_FORMAT_LAYOUT_SUBSAMPLED ||
layout == UTIL_FORMAT_LAYOUT_PLANAR2 ||
layout == UTIL_FORMAT_LAYOUT_PLANAR3;
}
#ifdef PAN_ARCH
static inline const struct pan_format *
GENX(pan_format_from_pipe_format)(enum pipe_format f)
{
return &GENX(pan_pipe_format)[f];
}
static inline const struct pan_blendable_format *
GENX(pan_blendable_format_from_pipe_format)(enum pipe_format f)
{
return &GENX(pan_blendable_formats)[f];
}
#if PAN_ARCH >= 6
static inline unsigned
GENX(pan_dithered_format_from_pipe_format)(enum pipe_format f, bool dithered)
{
mali_pixel_format pixfmt = GENX(pan_blendable_formats)[f].bifrost[dithered];
/* Formats requiring blend shaders are stored raw in the tilebuffer and will
* have 0 as their pixel format. Assumes dithering is set, I don't know of a
* case when it makes sense to turn off dithering. */
return pixfmt ?: GENX(pan_format_from_pipe_format)(f)->hw;
}
#endif
static inline bool
GENX(pan_format_supports_hw_blend)(enum pipe_format format)
{
return GENX(pan_blendable_format_from_pipe_format)(format)->internal;
}
static inline bool
GENX(pan_format_supports_msaa_average)(enum pipe_format format)
{
if (!GENX(pan_format_supports_hw_blend)(format))
return false;
/* F16 formats are blendable on v10 but don't support averaging until v11 */
if (util_format_is_float16(format) && PAN_ARCH < 11)
return false;
return true;
}
#endif
#endif
+354
View File
@@ -0,0 +1,354 @@
/*
* Copyright (C) 2008 VMware, Inc.
* Copyright (C) 2014 Broadcom
* Copyright (C) 2018-2019 Alyssa Rosenzweig
* Copyright (C) 2019-2020 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#ifndef __PAN_IMAGE_H
#define __PAN_IMAGE_H
#include "genxml/gen_macros.h"
#include <stdbool.h>
#include "util/format/u_format.h"
#include "pan_format.h"
#include "pan_layout.h"
#include "pan_mod.h"
#include "pan_props.h"
#include "kmod/pan_kmod.h"
#include "util/log.h"
#ifdef __cplusplus
extern "C" {
#endif
struct pan_mod_handler;
struct pan_image_plane {
struct pan_image_layout layout;
uint64_t base;
};
struct pan_image {
struct pan_image_props props;
const struct pan_mod_handler *mod_handler;
struct pan_image_plane *planes[MAX_IMAGE_PLANES];
};
struct pan_image_plane_ref {
struct pan_image *image;
uint32_t plane_idx;
};
struct pan_image_view {
/* Format, dimension and sample count of the view might differ from
* those of the image (2D view of a 3D image surface for instance).
*/
enum pipe_format format;
enum mali_texture_dimension dim;
unsigned first_level, last_level;
unsigned first_layer, last_layer;
float min_lod;
unsigned char swizzle[4];
/* planes 1 and 2 are NULL for single plane formats */
struct pan_image_plane_ref planes[MAX_IMAGE_PLANES];
/* If EXT_multisampled_render_to_texture is used, this may be
* greater than image->layout.nr_samples. */
unsigned nr_samples;
struct {
unsigned narrow;
unsigned hdr;
} astc;
};
static inline struct pan_image_plane_ref
pan_image_view_get_plane(const struct pan_image_view *iview, uint32_t idx)
{
if (idx >= ARRAY_SIZE(iview->planes))
return (struct pan_image_plane_ref){0};
return iview->planes[idx];
}
static inline unsigned
pan_image_view_get_plane_mask(const struct pan_image_view *iview)
{
unsigned mask = 0;
for (unsigned i = 0; i < ARRAY_SIZE(iview->planes); i++) {
if (iview->planes[i].image)
mask |= BITFIELD_BIT(i);
}
return mask;
}
static inline unsigned
pan_image_view_get_first_plane_idx(const struct pan_image_view *iview)
{
unsigned mask = pan_image_view_get_plane_mask(iview);
assert(mask);
return ffs(mask) - 1;
}
static inline struct pan_image_plane_ref
pan_image_view_get_first_plane(const struct pan_image_view *iview)
{
unsigned first_plane_idx = pan_image_view_get_first_plane_idx(iview);
return pan_image_view_get_plane(iview, first_plane_idx);
}
static inline uint32_t
pan_image_view_get_nr_samples(const struct pan_image_view *iview)
{
const struct pan_image_plane_ref pref = pan_image_view_get_first_plane(iview);
if (!pref.image)
return 0;
return pref.image->props.nr_samples;
}
static inline uint32_t
pan_image_view_get_layer_count(const struct pan_image_view *iview)
{
const struct pan_image_plane_ref pref = pan_image_view_get_first_plane(iview);
if (!pref.image)
return 0;
return iview->dim == MALI_TEXTURE_DIMENSION_3D
? pref.image->props.extent_px.depth
: iview->last_layer - iview->first_layer + 1;
}
static inline struct pan_image_plane_ref
pan_image_view_get_color_plane(const struct pan_image_view *iview)
{
/* We only support rendering to plane 0 */
assert(pan_image_view_get_plane(iview, 1).image == NULL);
return pan_image_view_get_plane(iview, 0);
}
static inline bool
pan_image_view_has_crc(const struct pan_image_view *iview)
{
const struct pan_image_plane_ref p = pan_image_view_get_color_plane(iview);
if (!p.image)
return false;
return p.image->props.crc;
}
static inline struct pan_image_plane_ref
pan_image_view_get_s_plane(const struct pan_image_view *iview)
{
ASSERTED const struct util_format_description *fdesc =
util_format_description(iview->format);
assert(util_format_has_stencil(fdesc));
/* In case of multiplanar depth/stencil, the stencil is always on
* plane 1. Combined depth/stencil only has one plane, so depth
* will be on plane 0 in either case.
*/
const struct pan_image_plane_ref pref =
iview->planes[1].image ? iview->planes[1] : iview->planes[0];
assert(pref.image);
fdesc = util_format_description(pref.image->props.format);
assert(util_format_has_stencil(fdesc));
return pref;
}
static inline struct pan_image_plane_ref
pan_image_view_get_zs_plane(const struct pan_image_view *iview)
{
assert(util_format_is_depth_or_stencil(iview->format));
/* Depth or combined depth-stencil is always on plane 0. */
return pan_image_view_get_plane(iview, 0);
}
static inline void
pan_image_view_check(const struct pan_image_view *iview)
{
#ifndef NDEBUG
unsigned nplanes = util_format_get_num_planes(iview->format);
struct pan_image_plane_ref pref;
for (unsigned i = 0; i < nplanes; i++) {
if (util_format_is_depth_or_stencil(iview->format)) {
const struct util_format_description *fdesc =
util_format_description(iview->format);
if (util_format_has_stencil(fdesc))
pref = pan_image_view_get_s_plane(iview);
else
pref = pan_image_view_get_zs_plane(iview);
} else {
pref = iview->planes[i];
}
/* Make sure we have an image and the plane we point to exists. */
assert(pref.image);
assert(pref.plane_idx <
util_format_get_num_planes(pref.image->props.format));
enum pipe_format view_format =
util_format_get_plane_format(iview->format, i);
enum pipe_format img_format =
util_format_get_plane_format(pref.image->props.format, pref.plane_idx);
/* View-based pixel re-interpretation only allowed if the formats
* blocksize match. Exception is for Z24X8 with AFBC enabled since then
* we can lower it to Z24 packed format internally. */
assert((util_format_get_blocksize(view_format) ==
util_format_get_blocksize(img_format)) ||
(view_format == PIPE_FORMAT_Z24X8_UNORM &&
img_format == PIPE_FORMAT_Z24_UNORM_PACKED &&
drm_is_afbc(pref.image->props.modifier)));
}
#endif
}
static inline uint64_t
pan_image_mip_level_size(const struct pan_image *image, unsigned plane_idx,
unsigned mip_level)
{
assert(plane_idx < ARRAY_SIZE(image->planes) &&
plane_idx < util_format_get_num_planes(image->props.format));
assert(mip_level < image->props.nr_slices);
assert(image->planes[plane_idx]);
uint64_t size = image->planes[plane_idx]->layout.slices[mip_level].size_B;
/* If this is an array, we need to cover the whole array. */
if (image->props.array_size > 1) {
size += image->planes[plane_idx]->layout.array_stride_B *
(image->props.array_size - 1);
}
return size;
}
static inline uint32_t
pan_image_get_wsi_row_pitch(const struct pan_image *image, unsigned plane_idx,
unsigned mip_level)
{
assert(image->mod_handler);
assert(image->mod_handler->get_wsi_row_pitch);
assert(plane_idx < ARRAY_SIZE(image->planes) &&
plane_idx < util_format_get_num_planes(image->props.format));
assert(image->planes[plane_idx]);
return image->mod_handler->get_wsi_row_pitch(image, plane_idx, mip_level);
}
static inline uint64_t
pan_image_get_wsi_offset(const struct pan_image *image, unsigned plane_idx,
unsigned mip_level)
{
assert(plane_idx < ARRAY_SIZE(image->planes) &&
plane_idx < util_format_get_num_planes(image->props.format));
assert(mip_level < image->props.nr_slices);
assert(image->planes[plane_idx]);
return image->planes[plane_idx]->layout.slices[mip_level].offset_B;
}
bool pan_image_layout_init(
unsigned arch, struct pan_image *image, unsigned plane_idx,
const struct pan_image_layout_constraints *explicit_layout_constraints);
struct pan_image_usage {
/* PAN_BIND_xxx flags. */
uint32_t bind;
/* Image needs to be mappable at standard sparse block granularity. */
bool standard_sparse_mapping_granularity;
/* Image filled directly from the CPU. */
bool host_copy;
/* Image frequently updated with host data. */
bool frequent_host_updates;
/* Legacy scanout image. */
bool legacy_scanout;
/* Image created by WSI. */
bool wsi;
};
static inline enum pan_mod_support
pan_image_test_props(const struct pan_kmod_dev_props *dprops,
const struct pan_image_props *iprops,
const struct pan_image_usage *iusage)
{
const unsigned arch = pan_arch(dprops->gpu_id);
struct pan_image image = {
.props = *iprops,
.mod_handler = pan_mod_get_handler(arch, iprops->modifier),
};
if (!image.mod_handler)
return PAN_MOD_NOT_SUPPORTED;
enum pan_mod_support ret =
image.mod_handler->test_props(dprops, &image.props, iusage);
if (ret == PAN_MOD_NOT_SUPPORTED)
return ret;
/* Now make sure the layout can be properly initialized on all planes. */
uint32_t plane_count = util_format_get_num_planes(image.props.format);
for (uint32_t p = 0; p < plane_count; p++) {
struct pan_image_plane plane;
memset(&plane, 0, sizeof(plane));
image.planes[p] = &plane;
if (!pan_image_layout_init(arch, &image, p, NULL))
return PAN_MOD_NOT_SUPPORTED;
}
return ret;
}
static inline bool
pan_image_test_modifier_with_format(const struct pan_kmod_dev_props *dprops,
uint64_t modifier, enum pipe_format format)
{
/* To check if a <modifier,format> pair is supported, we define the smallest
* possible 2D image (or 3D image if this is a 3D compressed format). */
const struct pan_image_props iprops = {
.modifier = modifier,
.format = format,
.extent_px = {
.width = util_format_get_blockwidth(format),
.height = util_format_get_blockheight(format),
.depth = util_format_get_blockdepth(format),
},
.nr_samples = 1,
.dim = util_format_get_blockdepth(format) > 1 ? MALI_TEXTURE_DIMENSION_3D
: MALI_TEXTURE_DIMENSION_2D,
.nr_slices = 1,
.array_size = 1,
};
return pan_image_test_props(dprops, &iprops, NULL) != PAN_MOD_NOT_SUPPORTED;
}
#ifdef __cplusplus
} /* extern C */
#endif
#endif
+248
View File
@@ -0,0 +1,248 @@
/*
* Copyright (C) 2019-2020 Collabora Ltd.
* Copyright (C) 2019 Alyssa Rosenzweig
* Copyright (C) 2014-2017 Broadcom
* SPDX-License-Identifier: MIT
*/
#ifndef __PAN_JC_H__
#define __PAN_JC_H__
#include "genxml/gen_macros.h"
#include "pan_pool.h"
/* Job chain */
struct pan_jc {
/* The first job in the chain */
uint64_t first_job;
/* The number of jobs in the chain, essentially */
unsigned job_index;
/* A CPU-side pointer to the previous job for next_job linking */
struct mali_job_header_packed *prev_job;
/* A CPU-side pointer to the first tiler job for dep updates when
* injecting a reload tiler job.
*/
struct mali_job_header_packed *first_tiler;
uint32_t first_tiler_dep1;
/* The dependency for tiler jobs (i.e. the index of the last emitted
* tiler job, or zero if none have been emitted) */
unsigned tiler_dep;
/* The job index of the WRITE_VALUE job (before it has been created) */
unsigned write_value_index;
};
#if defined(PAN_ARCH) && PAN_ARCH <= 9
/*
* There are various types of Mali jobs:
*
* - WRITE_VALUE: generic write primitive, used to zero tiler field
* - VERTEX: runs a vertex shader
* - TILER: runs tiling and sets up a fragment shader
* - FRAGMENT: runs fragment shaders and writes out
* - COMPUTE: runs a compute shader
* - FUSED: vertex+tiler fused together, implicit intradependency (Bifrost)
* - GEOMETRY: runs a geometry shader (unimplemented)
* - CACHE_FLUSH: unseen in the wild, theoretically cache flush
*
* In between a full batch and a single Mali job is the "job chain", a series
* of Mali jobs together forming a linked list. Within the job chain, each Mali
* job can set (up to) two dependencies on other earlier jobs in the chain.
* This dependency graph forms a scoreboard. The general idea of a scoreboard
* applies: when there is a data dependency of job B on job A, job B sets one
* of its dependency indices to job A, ensuring that job B won't start until
* job A finishes.
*
* More specifically, here are a set of rules:
*
* - A write value job must appear if and only if there is at least one tiler
* job, and tiler jobs must depend on it.
*
* - Vertex jobs and tiler jobs are independent.
*
* - A tiler job must have a dependency on its data source. If it's getting
* data from a vertex job, it depends on the vertex job. If it's getting data
* from software, this is null.
*
* - Tiler jobs must depend on the write value job (chained or otherwise).
*
* - Tiler jobs must be strictly ordered. So each tiler job must depend on the
* previous job in the chain.
*
* - Jobs linking via next_job has no bearing on order of execution, rather it
* just establishes the linked list of jobs, EXCEPT:
*
* - A job's dependencies must appear earlier in the linked list (job chain).
*
* Justification for each rule:
*
* - Write value jobs are used to write a zero into a magic tiling field, which
* enables tiling to work. If tiling occurs, they are needed; if it does not,
* we cannot emit them since then tiling partially occurs and it's bad.
*
* - The hardware has no notion of a "vertex/tiler job" (at least not our
* hardware -- other revs have fused jobs, but --- crap, this just got even
* more complicated). They are independent units that take in data, process
* it, and spit out data.
*
* - Any job must depend on its data source, in fact, or risk a
* read-before-write hazard. Tiler jobs get their data from vertex jobs, ergo
* tiler jobs depend on the corresponding vertex job (if it's there).
*
* - The tiler is not thread-safe; this dependency prevents race conditions
* between two different jobs trying to write to the tiler outputs at the
* same time.
*
* - Internally, jobs are scoreboarded; the next job fields just form a linked
* list to allow the jobs to be read in; the execution order is from
* resolving the dependency fields instead.
*
* - The hardware cannot set a dependency on a job it doesn't know about yet,
* and dependencies are processed in-order of the next job fields.
*
*/
/**
* Does this job use the tiler? Beyond tiler jobs, index-driven vertex shading
* jobs also do.
*/
static inline bool
job_uses_tiling(enum mali_job_type type)
{
#if PAN_ARCH >= 9
if (type == MALI_JOB_TYPE_MALLOC_VERTEX)
return true;
#endif
#if PAN_ARCH >= 6
if (type == MALI_JOB_TYPE_INDEXED_VERTEX)
return true;
#endif
return (type == MALI_JOB_TYPE_TILER);
}
/* Generates, uploads, and queues a a new job. All fields are written in order
* except for next_job accounting (TODO: Should we be clever and defer the
* upload of the header here until next job to keep the access pattern totally
* linear? Or is that just a micro op at this point?). Returns the generated
* index for dep management.
*
* Inject is used to inject a job at the front, for wallpapering. If you are
* not wallpapering and set this, dragons will eat you. */
static inline unsigned
pan_jc_add_job(struct pan_jc *jc, enum mali_job_type type, bool barrier,
bool suppress_prefetch, unsigned local_dep, unsigned global_dep,
const struct pan_ptr *job, bool inject)
{
if (job_uses_tiling(type)) {
/* Tiler jobs must be chained, and on Midgard, the first tiler
* job must depend on the write value job, whose index we
* reserve now */
if (PAN_ARCH <= 5 && !jc->write_value_index)
jc->write_value_index = ++jc->job_index;
if (jc->tiler_dep && !inject)
global_dep = jc->tiler_dep;
else if (PAN_ARCH <= 5)
global_dep = jc->write_value_index;
}
/* Assign the index */
unsigned index = ++jc->job_index;
pan_cast_and_pack(job->cpu, JOB_HEADER, header) {
header.type = type;
header.barrier = barrier;
header.suppress_prefetch = suppress_prefetch;
header.index = index;
header.dependency_1 = local_dep;
header.dependency_2 = global_dep;
if (inject)
header.next = jc->first_job;
}
if (inject) {
assert(type == MALI_JOB_TYPE_TILER && "only for blit shaders");
if (jc->first_tiler) {
/* Manual update of the dep2 field. This is bad,
* don't copy this pattern.
*/
jc->first_tiler->opaque[5] =
jc->first_tiler_dep1 | (index << 16);
}
jc->first_tiler = (void *)job->cpu;
jc->first_tiler_dep1 = local_dep;
jc->first_job = job->gpu;
return index;
}
/* Form a chain */
if (job_uses_tiling(type)) {
if (!jc->first_tiler) {
jc->first_tiler = (void *)job->cpu;
jc->first_tiler_dep1 = local_dep;
}
jc->tiler_dep = index;
}
if (jc->prev_job) {
/* Manual update of the next pointer. This is bad, don't copy
* this pattern.
* TODO: Find a way to defer last job header emission until we
* have a new job to queue or the batch is ready for execution.
*/
jc->prev_job->opaque[6] = job->gpu;
jc->prev_job->opaque[7] = job->gpu >> 32;
} else {
jc->first_job = job->gpu;
}
jc->prev_job = (struct mali_job_header_packed *)job->cpu;
return index;
}
/* Generates a write value job, used to initialize the tiler structures. Note
* this is called right before frame submission. */
static inline struct pan_ptr
pan_jc_initialize_tiler(struct pan_pool *pool, struct pan_jc *jc,
uint64_t polygon_list)
{
struct pan_ptr transfer = {0};
/* Check if we even need tiling */
if (PAN_ARCH >= 6 || !jc->first_tiler)
return transfer;
/* Okay, we do. Let's generate it. We'll need the job's polygon list
* regardless of size. */
transfer = pan_pool_alloc_desc(pool, WRITE_VALUE_JOB);
pan_section_pack(transfer.cpu, WRITE_VALUE_JOB, HEADER, header) {
header.type = MALI_JOB_TYPE_WRITE_VALUE;
header.index = jc->write_value_index;
header.next = jc->first_job;
}
pan_section_pack(transfer.cpu, WRITE_VALUE_JOB, PAYLOAD, payload) {
payload.address = polygon_list;
payload.type = MALI_WRITE_VALUE_TYPE_ZERO;
}
jc->first_job = transfer.gpu;
return transfer;
}
#endif /* PAN_ARCH */
#endif
+161
View File
@@ -0,0 +1,161 @@
/*
* Copyright (C) 2019-2022 Collabora, Ltd.
* Copyright (C) 2018-2019 Alyssa Rosenzweig
* SPDX-License-Identifier: MIT
*/
#include "pan_layout.h"
#include "util/log.h"
#include "util/macros.h"
#include "util/u_math.h"
#include "pan_afbc.h"
#include "pan_afrc.h"
#include "pan_image.h"
#include "pan_mod.h"
#include "pan_props.h"
/*
* Computes sizes for checksumming, which is 8 bytes per 16x16 tile.
* Checksumming is believed to be a CRC variant (CRC64 based on the size?).
* This feature is also known as "transaction elimination".
* CRC values are prefetched by 32x32 (64x64 on v12+) regions so size needs to
* be aligned.
*/
#define CHECKSUM_TILE_WIDTH 16
#define CHECKSUM_TILE_HEIGHT 16
#define CHECKSUM_BYTES_PER_TILE 8
static void
init_slice_crc_info(unsigned arch, struct pan_image_slice_layout *slice,
unsigned width_px, unsigned height_px, uint64_t offset_B)
{
unsigned checksum_region_size_px = pan_meta_tile_size(arch);
unsigned checksum_x_tile_per_region =
(checksum_region_size_px / CHECKSUM_TILE_WIDTH);
unsigned checksum_y_tile_per_region =
(checksum_region_size_px / CHECKSUM_TILE_HEIGHT);
unsigned tile_count_x = checksum_x_tile_per_region *
DIV_ROUND_UP(width_px, checksum_region_size_px);
unsigned tile_count_y = checksum_y_tile_per_region *
DIV_ROUND_UP(height_px, checksum_region_size_px);
slice->crc.offset_B = offset_B;
slice->crc.stride_B = tile_count_x * CHECKSUM_BYTES_PER_TILE;
slice->crc.size_B = slice->crc.stride_B * tile_count_y;
}
static struct pan_image_extent
get_mip_level_extent(const struct pan_image_props *props, unsigned plane_idx,
unsigned mip_level)
{
return (struct pan_image_extent){
.width = u_minify(util_format_get_plane_width(props->format, plane_idx,
props->extent_px.width),
mip_level),
.height = u_minify(util_format_get_plane_height(props->format, plane_idx,
props->extent_px.height),
mip_level),
.depth = u_minify(props->extent_px.depth, mip_level),
};
}
bool
pan_image_layout_init(
unsigned arch, struct pan_image *image, unsigned plane_idx,
const struct pan_image_layout_constraints *explicit_layout_constraints)
{
assert(image->mod_handler);
assert(plane_idx < ARRAY_SIZE(image->planes) &&
plane_idx < util_format_get_num_planes(image->props.format));
assert(image->planes[plane_idx]);
const struct pan_mod_handler *mod_handler = image->mod_handler;
const struct pan_image_props *props = &image->props;
struct pan_image_layout *layout = &image->planes[plane_idx]->layout;
/* Use explicit layout only when wsi_row_pitch_B is non-zero */
struct pan_image_layout_constraints layout_constraints = {0};
if (explicit_layout_constraints)
layout_constraints = *explicit_layout_constraints;
const bool use_explicit_layout = layout_constraints.wsi_row_pitch_B != 0;
/* Explicit stride only work with non-mipmap, non-array, single-sample
* 2D image without CRC.
*/
if (use_explicit_layout &&
(props->extent_px.depth > 1 || props->nr_samples > 1 ||
props->array_size > 1 || props->dim != MALI_TEXTURE_DIMENSION_2D ||
props->nr_slices > 1 || props->crc))
return false;
if (plane_idx >= util_format_get_num_planes(props->format))
return false;
/* Init plane layout data. */
if (mod_handler->init_plane_layout &&
!mod_handler->init_plane_layout(props, plane_idx, layout))
return false;
/* MSAA is implemented as a 3D texture with z corresponding to the
* sample #, horrifyingly enough */
assert(props->extent_px.depth == 1 || props->nr_samples == 1);
/* Make sure the extent/sample_count is not zero. */
assert(props->extent_px.width && props->extent_px.height &&
props->extent_px.depth && props->nr_samples);
struct pan_image_extent mip_extent_px = {
.width = util_format_get_plane_width(props->format, plane_idx,
props->extent_px.width),
.height = util_format_get_plane_height(props->format, plane_idx,
props->extent_px.height),
.depth = props->extent_px.depth,
};
for (unsigned l = 0; l < props->nr_slices; ++l) {
struct pan_image_slice_layout *slayout = &layout->slices[l];
if (!mod_handler->init_slice_layout(props, plane_idx, mip_extent_px,
&layout_constraints, slayout))
return false;
layout_constraints.offset_B += slayout->size_B;
/* Add a checksum region if necessary */
if (props->crc) {
init_slice_crc_info(arch, slayout, mip_extent_px.width,
mip_extent_px.height, layout_constraints.offset_B);
layout_constraints.offset_B += slayout->crc.size_B;
slayout->size_B += slayout->crc.size_B;
}
mip_extent_px.width = u_minify(mip_extent_px.width, 1);
mip_extent_px.height = u_minify(mip_extent_px.height, 1);
mip_extent_px.depth = u_minify(mip_extent_px.depth, 1);
}
/* Arrays and cubemaps have the entire miptree duplicated */
layout->array_stride_B =
ALIGN_POT(layout_constraints.offset_B - layout->slices[0].offset_B, 64);
if (use_explicit_layout) {
layout->data_size_B =
layout_constraints.offset_B - explicit_layout_constraints->offset_B;
} else {
/* Native images start from offset 0, and the planar plane offset has
* been at least 4K page aligned below. So the base level slice offset
* should always be the same with the plane offset.
*/
assert(!explicit_layout_constraints ||
explicit_layout_constraints->offset_B ==
layout->slices[0].offset_B);
layout->data_size_B = ALIGN_POT(
(uint64_t)layout->array_stride_B * (uint64_t)props->array_size, 4096);
}
return true;
}
+267
View File
@@ -0,0 +1,267 @@
/*
* Copyright (C) 2008 VMware, Inc.
* Copyright (C) 2014 Broadcom
* Copyright (C) 2018-2019 Alyssa Rosenzweig
* Copyright (C) 2019-2020 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#ifndef __PAN_LAYOUT_H
#define __PAN_LAYOUT_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include "genxml/gen_macros.h"
#include "util/format/u_format.h"
#define MAX_MIP_LEVELS 17
#define MAX_IMAGE_PLANES 3
struct pan_mod_handler;
struct pan_afbc_image_slice_layout {
struct {
/* Number of bytes between two rows of AFBC headers. */
uint32_t row_stride_B;
/* For 3D textures, this is the size in bytes of AFBC headers covering
* a single Z slice. For 2D this is the total header size. This size is
* the utile header size, it doesn't count the padding needed to meet the
* body alignment constraints. Pass this to pan_afbc_body_offset() to get
* the body offset.
*/
uint32_t surface_size_B;
} header;
/* For 3D textures, this is the stride in bytes between AFBC headers of two
* consecutive Z slices. For 2D, this is the total size of the 2D level.
*/
uint64_t surface_stride_B;
};
struct pan_tiled_or_linear_image_slice_layout {
/* Number of bytes between two rows of tiles/lines. */
uint32_t row_stride_B;
/* For 3D textures, this is the stride in bytes between two
* consecutive Z slices. For 2DMS textures, this is the stride in bytes
* between two sample planes.
*/
uint64_t surface_stride_B;
};
struct pan_image_slice_layout {
/* Offset in bytes relative to the base bo bound.
*
* Unlike gallium, vulkan has to report explicit image subres layout which
* disallows to hide the planar plane offset into the bo mapping. So we let
* the slice offsets to include the plane offset of the native multi-planar
* images to be consistent with the imported ones via explicit layout info.
* Doing so allows us to use a single code path to correctly:
* - report image subres layout and memory requirement
* - bind image memory
*/
uint64_t offset_B;
/* Size of the MIP level in bytes. */
uint64_t size_B;
/* Some properties have a different meaning depending on the modifier.
* Those are placed in different structs under a union. */
union {
/* Used only for AFBC images. */
struct pan_afbc_image_slice_layout afbc;
/* Used for linear, u-tiled and AFRC images. */
struct pan_tiled_or_linear_image_slice_layout tiled_or_linear;
};
/* If checksumming is enabled following the slice, what
* is its offset/stride? */
struct {
uint64_t offset_B;
uint32_t stride_B;
uint32_t size_B;
} crc;
};
struct pan_image_extent {
unsigned width;
unsigned height;
unsigned depth;
};
struct pan_image_props {
uint64_t modifier;
enum pipe_format format;
struct pan_image_extent extent_px;
unsigned nr_samples;
enum mali_texture_dimension dim;
unsigned nr_slices;
unsigned array_size;
bool crc;
};
struct pan_image_layout {
struct pan_image_slice_layout slices[MAX_MIP_LEVELS];
union {
struct {
int mode;
} afbc;
};
/* Image plane data size in bytes */
uint64_t data_size_B;
uint64_t array_stride_B;
};
struct pan_image_layout_constraints {
/*
* Plane offset in bytes
* - For native images, it's the planar plane offset.
* - For imported images, it's the user specified explicit offset.
*
* To be noted, this offset might be adjusted to choose an optimal alignment,
* unless the layout constraints are explicit (wsi_row_patch_B != 0).
*/
uint64_t offset_B;
/* Row pitch in bytes. Non-zero if layout is explicit. */
uint32_t wsi_row_pitch_B;
/* When true, AFBC/AFRC imports are stricter than they were when those
* modifiers where introduced. */
bool strict;
};
/*
* Represents the block size of a single plane. For AFBC, this represents the
* superblock size. For u-interleaving, this represents the tile size.
*/
struct pan_image_block_size {
/** Width of block */
unsigned width;
/** Height of blocks */
unsigned height;
};
/*
* Determine the required alignment for the slice offset of an image. For
* now, this is always aligned on 64-byte boundaries. */
static inline uint32_t
pan_image_slice_align(uint64_t modifier)
{
return 64;
}
static inline uint32_t
pan_linear_or_tiled_row_align_req(unsigned arch, enum pipe_format format,
unsigned plane_idx)
{
if (arch < 7) {
unsigned nplanes = util_format_get_num_planes(format);
/* If this is a planar format, align on the plane blocksize. */
if (nplanes > 1) {
enum pipe_format plane_format =
util_format_get_plane_format(format, plane_idx);
return util_next_power_of_two(util_format_get_blocksize(plane_format));
}
/* Align on blocksize if the format is compressed. */
if (util_format_is_compressed(format))
return util_next_power_of_two(util_format_get_blocksize(format));
const struct util_format_description *fdesc =
util_format_description(format);
unsigned comp_sz_bits = 0;
for (unsigned i = 0; i < ARRAY_SIZE(fdesc->channel); i++) {
if (!fdesc->channel[0].size)
continue;
/* Align on a pixel if any component is not 8-bit aligned or not a
* power of two. */
if (fdesc->channel[0].size % 8 != 0 ||
!util_is_power_of_two_nonzero(fdesc->channel[0].size))
return util_next_power_of_two(util_format_get_blocksize(format));
/* Align on a pixel if not all components have the same size. */
if (comp_sz_bits != 0 && comp_sz_bits != fdesc->channel[0].size)
return util_next_power_of_two(util_format_get_blocksize(format));
comp_sz_bits = fdesc->channel[0].size;
}
/* If all components are the same size, 8-bit aligned and a power of two,
* align on a component. */
return comp_sz_bits / 8;
}
switch (format) {
/* For v7+, NV12/NV21/I420 have a looser alignment requirement of 16 bytes */
case PIPE_FORMAT_R8G8B8_420_UNORM_PACKED:
case PIPE_FORMAT_R8_G8B8_420_UNORM:
case PIPE_FORMAT_G8_B8R8_420_UNORM:
case PIPE_FORMAT_R8_G8_B8_420_UNORM:
case PIPE_FORMAT_R8_B8_G8_420_UNORM:
case PIPE_FORMAT_R8_G8B8_422_UNORM:
case PIPE_FORMAT_R8_B8G8_422_UNORM:
return 16;
/* the 10 bit formats have even looser alignment */
case PIPE_FORMAT_R10G10B10_420_UNORM_PACKED:
case PIPE_FORMAT_R10_G10B10_420_UNORM:
case PIPE_FORMAT_R10_G10B10_422_UNORM:
return 1;
default:
return 64;
}
}
/*
* Given a format, determine the tile size used for u-interleaving. For formats
* that are already block compressed, this is 4x4. For all other formats, this
* is 16x16, hence the modifier name.
*/
static inline struct pan_image_block_size
pan_u_interleaved_tile_size_el(enum pipe_format format)
{
if (util_format_is_compressed(format)) {
return (struct pan_image_block_size){4, 4};
} else {
assert(16 % util_format_get_blockwidth(format) == 0);
assert(16 % util_format_get_blockheight(format) == 0);
return (struct pan_image_block_size){
.width = 16 / util_format_get_blockwidth(format),
.height = 16 / util_format_get_blockheight(format),
};
}
}
/* Given a format, determine the tile size used for interleaved 64k. */
static inline struct pan_image_block_size
pan_interleaved_64k_tile_size_el(enum pipe_format format)
{
switch (util_format_get_blocksize(format)) {
case 1: return (struct pan_image_block_size){256, 256};
case 2: return (struct pan_image_block_size){256, 128};
case 4: return (struct pan_image_block_size){128, 128};
case 8: return (struct pan_image_block_size){128, 64};
case 16: return (struct pan_image_block_size){64, 64};
default:
UNREACHABLE("unsupported format");
return (struct pan_image_block_size){0, 0};
}
}
#ifdef __cplusplus
} /* extern C */
#endif
#endif
+826
View File
@@ -0,0 +1,826 @@
/*
* Copyright (C) 2019-2025 Collabora, Ltd.
* Copyright (C) 2018-2019 Alyssa Rosenzweig
* SPDX-License-Identifier: MIT
*/
#include "pan_mod.h"
#include "pan_afbc.h"
#include "pan_afrc.h"
#include "pan_desc.h"
#include "pan_format.h"
#include "pan_image.h"
#include "pan_layout.h"
#include "pan_props.h"
#include "pan_texture.h"
#include "util/format/u_format.h"
static bool
pan_mod_afbc_match(uint64_t mod)
{
return drm_is_afbc(mod);
}
static uint32_t
pan_mod_afbc_get_wsi_row_pitch(const struct pan_image *image,
unsigned plane_idx, unsigned mip_level)
{
const struct pan_image_props *props = &image->props;
const struct pan_image_layout *layout = &image->planes[plane_idx]->layout;
const unsigned header_row_stride_B =
layout->slices[mip_level].afbc.header.row_stride_B;
const struct pan_image_block_size tile_extent_el =
pan_afbc_superblock_size_el(props->format, props->modifier);
const unsigned tile_payload_size_B =
tile_extent_el.width * tile_extent_el.height *
pan_format_get_plane_blocksize(props->format, plane_idx);
const unsigned tile_row_payload_size_B =
pan_afbc_stride_blocks(props->format,
props->modifier, header_row_stride_B) *
tile_payload_size_B;
return tile_row_payload_size_B / pan_afbc_superblock_height(props->modifier);
}
static bool
pan_mod_afbc_init_plane_layout(const struct pan_image_props *props,
unsigned plane_idx,
struct pan_image_layout *plane_layout)
{
enum pan_afbc_mode mode =
pan_afbc_format(PAN_ARCH, props->format, plane_idx);
if (mode == PAN_AFBC_MODE_INVALID)
return false;
plane_layout->afbc.mode = mode;
return true;
}
static bool
pan_mod_afbc_init_slice_layout(
const struct pan_image_props *props, unsigned plane_idx,
struct pan_image_extent mip_extent_px,
const struct pan_image_layout_constraints *layout_constraints,
struct pan_image_slice_layout *slayout)
{
/* Use explicit layout only when wsi_row_pitch_B is non-zero */
const bool use_explicit_layout =
layout_constraints && layout_constraints->wsi_row_pitch_B;
struct pan_image_block_size afbc_tile_extent_px =
pan_afbc_superblock_size(props->modifier);
unsigned offset_align_mask =
pan_afbc_header_align(PAN_ARCH, props->modifier) - 1;
unsigned row_align_mask = pan_afbc_header_row_stride_align(
PAN_ARCH, props->format, props->modifier) -
1;
struct pan_image_block_size afbc_tile_extent_el =
pan_afbc_superblock_size_el(props->format, props->modifier);
unsigned afbc_tile_payload_size_B =
afbc_tile_extent_el.width * afbc_tile_extent_el.height *
pan_format_get_plane_blocksize(props->format, plane_idx);
struct pan_image_block_size align_px =
pan_afbc_renderblock_size(props->modifier);
/* If superblock tiling is used, align on a superblock tile. */
if (props->modifier & AFBC_FORMAT_MOD_TILED) {
align_px.width =
ALIGN_POT(align_px.width, afbc_tile_extent_px.width *
pan_afbc_tile_size(props->format, props->modifier));
align_px.height =
ALIGN_POT(align_px.height, afbc_tile_extent_px.height *
pan_afbc_tile_size(props->format, props->modifier));
}
struct pan_image_extent aligned_extent_px = {
.width = ALIGN_POT(mip_extent_px.width, align_px.width),
.height = ALIGN_POT(mip_extent_px.height, align_px.height),
.depth = mip_extent_px.depth,
};
if (use_explicit_layout) {
unsigned afbc_tile_payload_row_stride_B =
layout_constraints->wsi_row_pitch_B *
pan_afbc_superblock_height(props->modifier);
/* For quite some time, we've been accepting WSI row pitch that
* didn't match exactly the image size and have been assuming tightly
* packed tile rows instead of using the explicit stride in that case.
* This is something we can't change without risking breaking existing
* users, so we enforce this explicit tile alignment only if we were
* asked to. */
if (layout_constraints->strict &&
(afbc_tile_payload_row_stride_B % afbc_tile_payload_size_B)) {
mesa_loge("WSI pitch is not aligned on an AFBC tile");
return false;
}
unsigned width_from_wsi_row_stride =
(afbc_tile_payload_row_stride_B / afbc_tile_payload_size_B) *
pan_afbc_superblock_width(props->modifier);
if (width_from_wsi_row_stride < mip_extent_px.width) {
mesa_loge("WSI pitch too small");
return false;
}
slayout->afbc.header.row_stride_B =
pan_afbc_row_stride(props->format, props->modifier, width_from_wsi_row_stride);
if (slayout->afbc.header.row_stride_B & row_align_mask) {
mesa_loge("WSI pitch not properly aligned");
return false;
}
slayout->offset_B = layout_constraints->offset_B;
if (slayout->offset_B & offset_align_mask) {
mesa_loge("WSI offset not properly aligned");
return false;
}
/* If this is not a strict import, ignore the WSI row pitch and use
* the resource width to get the size. */
if (!layout_constraints->strict) {
slayout->afbc.header.row_stride_B = ALIGN_POT(
pan_afbc_row_stride(props->format, props->modifier, aligned_extent_px.width),
row_align_mask + 1);
}
} else {
slayout->offset_B =
ALIGN_POT(layout_constraints ? layout_constraints->offset_B : 0,
offset_align_mask + 1);
slayout->afbc.header.row_stride_B = ALIGN_POT(
pan_afbc_row_stride(props->format, props->modifier, aligned_extent_px.width),
row_align_mask + 1);
}
const unsigned row_stride_sb = pan_afbc_stride_blocks(props->format,
props->modifier,
slayout->afbc.header.row_stride_B);
const unsigned surface_stride_sb =
row_stride_sb * (aligned_extent_px.height / afbc_tile_extent_px.height);
uint64_t hdr_surf_size_B =
(uint64_t)surface_stride_sb * AFBC_HEADER_BYTES_PER_TILE;
uint64_t body_offset_B =
pan_afbc_body_offset(PAN_ARCH, props->modifier, hdr_surf_size_B);
uint64_t surf_stride_B =
body_offset_B + ((uint64_t)surface_stride_sb * afbc_tile_payload_size_B);
slayout->afbc.header.surface_size_B = hdr_surf_size_B;
slayout->afbc.surface_stride_B = surf_stride_B;
slayout->size_B = surf_stride_B * mip_extent_px.depth;
if (hdr_surf_size_B > UINT32_MAX)
return false;
return true;
}
static enum pan_mod_support
pan_mod_afbc_test_props(const struct pan_kmod_dev_props *dprops,
const struct pan_image_props *iprops,
const struct pan_image_usage *iusage)
{
/* AFBC not supported. */
if (!pan_query_afbc(dprops))
return PAN_MOD_NOT_SUPPORTED;
/* Not all architectures support tiled mode */
if ((iprops->modifier & AFBC_FORMAT_MOD_TILED) && !pan_afbc_can_tile(PAN_ARCH))
return PAN_MOD_NOT_SUPPORTED;
/* ... or split mode */
if ((iprops->modifier & AFBC_FORMAT_MOD_SPLIT) && !pan_afbc_can_split(PAN_ARCH))
return PAN_MOD_NOT_SUPPORTED;
/* Check compatibility between formats and parameters */
unsigned plane_count = util_format_get_num_planes(iprops->format);
for (unsigned p = 0; p < plane_count; p++) {
enum pan_afbc_mode mode = pan_afbc_format(PAN_ARCH, iprops->format, p);
if (mode == PAN_AFBC_MODE_INVALID)
return PAN_MOD_NOT_SUPPORTED;
if (!pan_afbc_check_params(mode, iprops->modifier))
return PAN_MOD_NOT_SUPPORTED;
}
/* AFBC can't do multisampling. */
if (iprops->nr_samples > 1)
return PAN_MOD_NOT_SUPPORTED;
/* AFBC(2D) or AFBC(3D) on v7+ only. */
if ((iprops->dim == MALI_TEXTURE_DIMENSION_3D && PAN_ARCH < 7) ||
iprops->dim != MALI_TEXTURE_DIMENSION_2D)
return PAN_MOD_NOT_SUPPORTED;
/* No image store. */
if (iusage && iusage->bind & PAN_BIND_STORAGE_IMAGE)
return PAN_MOD_NOT_SUPPORTED;
/* ZS buffer descriptors can't pass split/wide/YTR modifiers. */
if (iusage && (iusage->bind & PAN_BIND_DEPTH_STENCIL) &&
(pan_afbc_superblock_width(iprops->modifier) != 16 ||
(iprops->modifier & (AFBC_FORMAT_MOD_SPLIT | AFBC_FORMAT_MOD_YTR))))
return PAN_MOD_NOT_SUPPORTED;
/* We don't implement mapping individual tiles with AFBC. */
if (iusage && iusage->standard_sparse_mapping_granularity)
return PAN_MOD_NOT_SUPPORTED;
struct pan_image_block_size superblock_extent_px = pan_afbc_superblock_size(iprops->modifier);
if (iprops->modifier & AFBC_FORMAT_MOD_TILED) {
struct pan_image_block_size tile_extent_px = {
superblock_extent_px.width * pan_afbc_tile_size(iprops->format, iprops->modifier),
superblock_extent_px.height * pan_afbc_tile_size(iprops->format, iprops->modifier),
};
/* Tiled mode has some overhead we don't want to pay if the image size is
* too thin. */
if (iprops->extent_px.width < tile_extent_px.width / 2 ||
iprops->extent_px.height < tile_extent_px.height / 2)
return PAN_MOD_NOT_OPTIMAL;
} else {
/* For images thinner than a superblock, fall through to U-interleaved. */
if (iprops->extent_px.width < superblock_extent_px.width ||
iprops->extent_px.height < superblock_extent_px.height)
return PAN_MOD_NOT_OPTIMAL;
}
/* Only use 32x8 superblock for WSI images. */
if (iusage && !iusage->wsi &&
pan_afbc_superblock_width(iprops->modifier) != 16)
return PAN_MOD_NOT_OPTIMAL;
/* Prefer YTR when available. */
if (pan_afbc_can_ytr(iprops->format) &&
!(iprops->modifier & AFBC_FORMAT_MOD_YTR))
return PAN_MOD_NOT_OPTIMAL;
/* Packing/unpacking AFBC payload requires a COMPUTE job which we'd rather
* avoid.
*/
if (iusage &&
(iusage->bind & (PAN_BIND_DEPTH_STENCIL | PAN_BIND_RENDER_TARGET)) &&
!(iprops->modifier & AFBC_FORMAT_MOD_SPARSE))
return PAN_MOD_NOT_OPTIMAL;
return PAN_MOD_OPTIMAL;
}
#define pan_mod_afbc_emit_tex_payload_entry \
GENX(pan_tex_emit_afbc_payload_entry)
#define pan_mod_afbc_emit_color_attachment GENX(pan_emit_afbc_color_attachment)
#define pan_mod_afbc_emit_zs_attachment GENX(pan_emit_afbc_zs_attachment)
#define pan_mod_afbc_emit_s_attachment GENX(pan_emit_afbc_s_attachment)
#if PAN_ARCH >= 10
static bool
pan_mod_afrc_match(uint64_t mod)
{
return drm_is_afrc(mod);
}
static enum pan_mod_support
pan_mod_afrc_test_props(const struct pan_kmod_dev_props *dprops,
const struct pan_image_props *iprops,
const struct pan_image_usage *iusage)
{
/* AFRC not supported. */
if (!pan_query_afrc(dprops))
return PAN_MOD_NOT_SUPPORTED;
/* Format not AFRC-able. */
if (!pan_afrc_supports_format(iprops->format))
return PAN_MOD_NOT_SUPPORTED;
/* AFRC does not support layered multisampling. */
if (iprops->nr_samples > 1)
return PAN_MOD_NOT_SUPPORTED;
/* No image store. */
if (iusage && iusage->bind & PAN_BIND_STORAGE_IMAGE)
return PAN_MOD_NOT_SUPPORTED;
/* We don't implement mapping individual tiles with AFRC. */
if (iusage && iusage->standard_sparse_mapping_granularity)
return PAN_MOD_NOT_SUPPORTED;
/* We can't write to an AFRC resource directly. */
if (iusage && iusage->host_copy)
return PAN_MOD_NOT_SUPPORTED;
/* Host updates require an extra blit which we would rather avoid. */
if (iusage && iusage->frequent_host_updates)
return PAN_MOD_NOT_OPTIMAL;
/* There's nothing preventing 1D AFRC, but it's pointless. */
if (iprops->dim == MALI_TEXTURE_DIMENSION_1D)
return PAN_MOD_NOT_OPTIMAL;
return PAN_MOD_OPTIMAL;
}
static uint32_t
pan_mod_afrc_get_wsi_row_pitch(const struct pan_image *image,
unsigned plane_idx, unsigned mip_level)
{
const struct pan_image_props *props = &image->props;
const struct pan_image_layout *layout = &image->planes[plane_idx]->layout;
const struct pan_image_block_size tile_extent_px =
pan_afrc_tile_size(props->format, props->modifier);
return layout->slices[mip_level].tiled_or_linear.row_stride_B /
tile_extent_px.height;
}
#define pan_mod_afrc_init_plane_layout NULL
static bool
pan_mod_afrc_init_slice_layout(
const struct pan_image_props *props, unsigned plane_idx,
struct pan_image_extent mip_extent_px,
const struct pan_image_layout_constraints *layout_constraints,
struct pan_image_slice_layout *slayout)
{
/* Use explicit layout only when wsi_row_pitch_B is non-zero */
const bool use_explicit_layout =
layout_constraints && layout_constraints->wsi_row_pitch_B;
const unsigned align_mask =
pan_afrc_buffer_alignment_from_modifier(props->modifier) - 1;
struct pan_image_block_size tile_extent_px =
pan_afrc_tile_size(props->format, props->modifier);
struct pan_image_extent aligned_extent_px = {
.width = ALIGN_POT(mip_extent_px.width, tile_extent_px.width),
.height = ALIGN_POT(mip_extent_px.height, tile_extent_px.height),
.depth = mip_extent_px.depth,
};
if (use_explicit_layout) {
slayout->tiled_or_linear.row_stride_B =
layout_constraints->wsi_row_pitch_B * tile_extent_px.height;
if (slayout->tiled_or_linear.row_stride_B & align_mask) {
mesa_loge("WSI pitch not properly aligned");
return false;
}
slayout->offset_B = layout_constraints->offset_B;
if (slayout->offset_B & align_mask) {
mesa_loge("WSI offset not properly aligned");
return false;
}
unsigned afrc_blk_size_B =
pan_afrc_block_size_from_modifier(props->modifier) *
AFRC_CLUMPS_PER_TILE;
unsigned width_from_wsi_row_stride =
(slayout->tiled_or_linear.row_stride_B / afrc_blk_size_B) *
tile_extent_px.width;
if (width_from_wsi_row_stride < mip_extent_px.width) {
mesa_loge("WSI pitch too small");
return false;
}
/* If this is not a strict import, ignore the WSI row pitch and use
* the resource width to get the size. */
if (!layout_constraints->strict) {
slayout->tiled_or_linear.row_stride_B = pan_afrc_row_stride(
props->format, props->modifier, mip_extent_px.width);
}
} else {
/* Align levels to cache-line as a performance improvement for
* linear/tiled and as a requirement for AFBC */
slayout->offset_B = ALIGN_POT(
layout_constraints ? layout_constraints->offset_B : 0, align_mask + 1);
slayout->tiled_or_linear.row_stride_B =
ALIGN_POT(pan_afrc_row_stride(props->format, props->modifier,
mip_extent_px.width),
align_mask + 1);
}
uint64_t surf_stride_B =
(uint64_t)slayout->tiled_or_linear.row_stride_B *
(aligned_extent_px.height / tile_extent_px.height);
slayout->tiled_or_linear.surface_stride_B = surf_stride_B;
slayout->size_B =
surf_stride_B * aligned_extent_px.depth * props->nr_samples;
return true;
}
#define pan_mod_afrc_emit_tex_payload_entry \
GENX(pan_tex_emit_afrc_payload_entry)
#define pan_mod_afrc_emit_color_attachment GENX(pan_emit_afrc_color_attachment)
#define pan_mod_afrc_emit_zs_attachment NULL
#define pan_mod_afrc_emit_s_attachment NULL
#endif
static bool
pan_mod_u_tiled_match(uint64_t mod)
{
return mod == DRM_FORMAT_MOD_ARM_16X16_BLOCK_U_INTERLEAVED;
}
static enum pan_mod_support
pan_mod_u_tiled_test_props(const struct pan_kmod_dev_props *dprops,
const struct pan_image_props *iprops,
const struct pan_image_usage *iusage)
{
assert(GENX(pan_format_from_pipe_format)(iprops->format)->hw);
/* YUV not supported. */
if (pan_format_is_yuv(iprops->format))
return PAN_MOD_NOT_SUPPORTED;
/* We don't implement mapping individual tiles in this layout. */
if (iusage && iusage->standard_sparse_mapping_granularity)
return PAN_MOD_NOT_SUPPORTED;
/* The purpose of tiling is improving locality in both X- and
* Y-directions. If there is only a single pixel in either direction,
* tiling does not make sense; using a linear layout instead is optimal
* for both memory usage and performance.
*/
if (MIN2(iprops->extent_px.width, iprops->extent_px.height) < 2)
return PAN_MOD_NOT_OPTIMAL;
return PAN_MOD_OPTIMAL;
}
static uint32_t
pan_mod_u_tiled_get_wsi_row_pitch(const struct pan_image *image,
unsigned plane_idx, unsigned mip_level)
{
const struct pan_image_props *props = &image->props;
const struct pan_image_layout *layout = &image->planes[plane_idx]->layout;
return layout->slices[mip_level].tiled_or_linear.row_stride_B /
pan_u_interleaved_tile_size_el(props->format).height;
}
#define pan_mod_u_tiled_init_plane_layout NULL
static bool
pan_mod_u_tiled_init_slice_layout(
const struct pan_image_props *props, unsigned plane_idx,
struct pan_image_extent mip_extent_px,
const struct pan_image_layout_constraints *layout_constraints,
struct pan_image_slice_layout *slayout)
{
/* Use explicit layout only when wsi_row_pitch_B is non-zero */
const bool use_explicit_layout =
layout_constraints && layout_constraints->wsi_row_pitch_B;
unsigned align_mask =
pan_linear_or_tiled_row_align_req(PAN_ARCH, props->format, plane_idx) - 1;
struct pan_image_block_size tile_extent_el =
pan_u_interleaved_tile_size_el(props->format);
struct pan_image_extent mip_extent_el;
unsigned tile_size_B;
if (util_format_is_compressed(props->format)) {
assert(util_format_get_num_planes(props->format) == 1);
mip_extent_el.width = DIV_ROUND_UP(
mip_extent_px.width, util_format_get_blockwidth(props->format));
mip_extent_el.height = DIV_ROUND_UP(
mip_extent_px.height, util_format_get_blockheight(props->format));
mip_extent_el.depth = DIV_ROUND_UP(
mip_extent_px.depth, util_format_get_blockdepth(props->format));
tile_size_B = tile_extent_el.width * tile_extent_el.height *
pan_format_get_plane_blocksize(props->format, plane_idx);
} else {
/* Block-based YUV needs special care, because the U-tile extent
* is in pixels, not blocks in that case. */
assert(tile_extent_el.width % util_format_get_blockwidth(props->format) ==
0);
assert(tile_extent_el.height %
util_format_get_blockheight(props->format) ==
0);
mip_extent_el = mip_extent_px;
tile_size_B =
(tile_extent_el.width / util_format_get_blockwidth(props->format)) *
(tile_extent_el.height / util_format_get_blockheight(props->format)) *
pan_format_get_plane_blocksize(props->format, plane_idx);
}
if (use_explicit_layout) {
slayout->tiled_or_linear.row_stride_B =
layout_constraints->wsi_row_pitch_B * tile_extent_el.height;
if (slayout->tiled_or_linear.row_stride_B & align_mask) {
mesa_loge("WSI pitch not properly aligned");
return false;
}
const unsigned width_from_wsi_row_stride =
(slayout->tiled_or_linear.row_stride_B / tile_size_B) *
tile_extent_el.width;
if (width_from_wsi_row_stride < mip_extent_el.width) {
mesa_loge("WSI pitch too small");
return false;
}
slayout->offset_B = layout_constraints->offset_B;
if (slayout->offset_B & align_mask) {
mesa_loge("WSI offset not properly aligned");
return false;
}
} else {
/* When we can decide of the layout, we want things aligned on at least a
* cacheline for performance reasons. */
align_mask = MAX2(align_mask, 63);
slayout->offset_B = ALIGN_POT(
layout_constraints ? layout_constraints->offset_B : 0,
MAX2(align_mask + 1, pan_image_slice_align(props->modifier)));
slayout->tiled_or_linear.row_stride_B = ALIGN_POT(
tile_size_B * DIV_ROUND_UP(mip_extent_el.width, tile_extent_el.width),
align_mask + 1);
}
uint64_t surf_stride_B =
(uint64_t)slayout->tiled_or_linear.row_stride_B *
DIV_ROUND_UP(mip_extent_el.height, tile_extent_el.height);
surf_stride_B = ALIGN_POT(surf_stride_B, (uint64_t)align_mask + 1);
slayout->tiled_or_linear.surface_stride_B = surf_stride_B;
slayout->size_B = surf_stride_B * mip_extent_el.depth * props->nr_samples;
return true;
}
#define pan_mod_u_tiled_emit_tex_payload_entry \
GENX(pan_tex_emit_u_tiled_payload_entry)
#define pan_mod_u_tiled_emit_color_attachment \
GENX(pan_emit_u_tiled_color_attachment)
#define pan_mod_u_tiled_emit_zs_attachment GENX(pan_emit_u_tiled_zs_attachment)
#define pan_mod_u_tiled_emit_s_attachment GENX(pan_emit_u_tiled_s_attachment)
#if PAN_ARCH >= 10
#define pan_mod_interleaved_64k_init_plane_layout NULL
static bool
pan_mod_interleaved_64k_init_slice_layout(
const struct pan_image_props *props, unsigned plane_idx,
struct pan_image_extent mip_extent_px,
const struct pan_image_layout_constraints *layout_constraints,
struct pan_image_slice_layout *slayout)
{
assert(!(layout_constraints && layout_constraints->wsi_row_pitch_B));
struct pan_image_block_size tile_extent_el =
pan_interleaved_64k_tile_size_el(props->format);
struct pan_image_block_size tile_extent_px = {
tile_extent_el.width * util_format_get_blockwidth(props->format),
tile_extent_el.height * util_format_get_blockheight(props->format),
};
uint64_t tile_size_B = 65536;
struct pan_image_extent mip_extent_tiles = {
DIV_ROUND_UP(mip_extent_px.width, tile_extent_px.width),
DIV_ROUND_UP(mip_extent_px.height, tile_extent_px.height),
mip_extent_px.depth,
};
uint64_t row_stride_B = mip_extent_tiles.width * tile_size_B;
uint64_t surf_stride_B = mip_extent_tiles.height * row_stride_B;
slayout->offset_B = layout_constraints ? layout_constraints->offset_B : 0;
slayout->size_B = mip_extent_tiles.depth * surf_stride_B;
slayout->tiled_or_linear.row_stride_B = row_stride_B;
slayout->tiled_or_linear.surface_stride_B = surf_stride_B;
return true;
}
static uint32_t
pan_mod_interleaved_64k_get_wsi_row_pitch(const struct pan_image *image,
unsigned plane_idx, unsigned mip_level)
{
UNREACHABLE("interleaved 64k cannot be used for wsi");
}
static bool
pan_mod_interleaved_64k_match(uint64_t mod)
{
return mod == DRM_FORMAT_MOD_ARM_INTERLEAVED_64K;
}
static enum pan_mod_support
pan_mod_interleaved_64k_test_props(const struct pan_kmod_dev_props *dprops,
const struct pan_image_props *iprops,
const struct pan_image_usage *iusage)
{
assert(GENX(pan_format_from_pipe_format)(iprops->format)->hw);
/* YUV not supported. */
if (pan_format_is_yuv(iprops->format))
return PAN_MOD_NOT_SUPPORTED;
/* Non-po2 byte texel blocks not supported. */
if (!util_is_power_of_two_nonzero(util_format_get_blocksize(iprops->format)))
return PAN_MOD_NOT_SUPPORTED;
/* We don't implement multisampling with this layout. */
if (iprops->nr_samples > 1)
return PAN_MOD_NOT_SUPPORTED;
/* We don't implement tiling/detiling of this layout on host. */
if (iusage && iusage->host_copy)
return PAN_MOD_NOT_SUPPORTED;
/* We don't respect wsi_row_pitch_B so this layout is not usable for WSI. */
if (iusage && iusage->wsi)
return PAN_MOD_NOT_SUPPORTED;
struct pan_image_block_size tile_extent_el =
pan_interleaved_64k_tile_size_el(iprops->format);
struct pan_image_block_size tile_extent_px = {
tile_extent_el.width * util_format_get_blockwidth(iprops->format),
tile_extent_el.height * util_format_get_blockheight(iprops->format),
};
/* If our image is too thin it might make more sense to use U-interleaved or
* even linear */
if (iprops->extent_px.width < tile_extent_px.width / 2 ||
iprops->extent_px.height < tile_extent_px.height / 2)
return PAN_MOD_NOT_OPTIMAL;
return PAN_MOD_OPTIMAL;
}
#define pan_mod_interleaved_64k_emit_tex_payload_entry \
GENX(pan_tex_emit_interleaved_64k_payload_entry)
#define pan_mod_interleaved_64k_emit_color_attachment \
GENX(pan_emit_interleaved_64k_color_attachment)
#define pan_mod_interleaved_64k_emit_zs_attachment GENX(pan_emit_interleaved_64k_zs_attachment)
#define pan_mod_interleaved_64k_emit_s_attachment GENX(pan_emit_interleaved_64k_s_attachment)
#endif
static bool
pan_mod_linear_match(uint64_t mod)
{
return mod == DRM_FORMAT_MOD_LINEAR;
}
static enum pan_mod_support
pan_mod_linear_test_props(const struct pan_kmod_dev_props *dprops,
const struct pan_image_props *iprops,
const struct pan_image_usage *iusage)
{
assert(GENX(pan_format_from_pipe_format)(iprops->format)->hw);
switch (iprops->format) {
case PIPE_FORMAT_R8G8B8_420_UNORM_PACKED:
case PIPE_FORMAT_R10G10B10_420_UNORM_PACKED:
case PIPE_FORMAT_X6R10X6G10_X6R10X6B10_422_UNORM:
/* These formats have no linear representation. */
return PAN_MOD_NOT_SUPPORTED;
default:
break;
}
/* We can't implement mapping of tiles at standard sparse granularity using
* this layout. */
if (iusage && iusage->standard_sparse_mapping_granularity)
return PAN_MOD_NOT_SUPPORTED;
/* We assume that all "better" mods have been tested before linear, and
* declare it as optimal so it's always picked when tested, unless it's
* not supported.
*/
return PAN_MOD_OPTIMAL;
}
static uint32_t
pan_mod_linear_get_wsi_row_pitch(const struct pan_image *image,
unsigned plane_idx, unsigned mip_level)
{
const struct pan_image_layout *layout = &image->planes[plane_idx]->layout;
return layout->slices[mip_level].tiled_or_linear.row_stride_B;
}
#define pan_mod_linear_init_plane_layout NULL
static bool
pan_mod_linear_init_slice_layout(
const struct pan_image_props *props, unsigned plane_idx,
struct pan_image_extent mip_extent_px,
const struct pan_image_layout_constraints *layout_constraints,
struct pan_image_slice_layout *slayout)
{
/* Use explicit layout only when wsi_row_pitch_B is non-zero */
const bool use_explicit_layout =
layout_constraints && layout_constraints->wsi_row_pitch_B;
unsigned align_mask =
pan_linear_or_tiled_row_align_req(PAN_ARCH, props->format, plane_idx) - 1;
const unsigned fmt_blksize_B =
pan_format_get_plane_blocksize(props->format, plane_idx);
struct pan_image_extent mip_extent_el;
if (util_format_is_compressed(props->format)) {
assert(util_format_get_num_planes(props->format) == 1);
mip_extent_el.width = DIV_ROUND_UP(
mip_extent_px.width, util_format_get_blockwidth(props->format));
mip_extent_el.height = DIV_ROUND_UP(
mip_extent_px.height, util_format_get_blockheight(props->format));
mip_extent_el.depth = DIV_ROUND_UP(
mip_extent_px.depth, util_format_get_blockdepth(props->format));
} else {
mip_extent_el = mip_extent_px;
}
if (use_explicit_layout) {
unsigned width_from_wsi_row_stride =
layout_constraints->wsi_row_pitch_B / fmt_blksize_B;
if (!util_format_is_compressed(props->format))
width_from_wsi_row_stride *= util_format_get_blockwidth(props->format);
if (width_from_wsi_row_stride < mip_extent_el.width) {
mesa_loge("WSI pitch too small");
return false;
}
slayout->tiled_or_linear.row_stride_B =
layout_constraints->wsi_row_pitch_B;
if (slayout->tiled_or_linear.row_stride_B & align_mask) {
mesa_loge("WSI pitch not properly aligned");
return false;
}
slayout->offset_B = layout_constraints->offset_B;
if (slayout->offset_B & align_mask) {
mesa_loge("WSI offset not properly aligned");
return false;
}
} else {
/* When we can decide of the layout, we want things aligned on at least a
* cacheline for performance reasons. */
align_mask = MAX2(align_mask, 63);
slayout->offset_B = ALIGN_POT(
layout_constraints ? layout_constraints->offset_B : 0,
MAX2(align_mask + 1, pan_image_slice_align(props->modifier)));
slayout->tiled_or_linear.row_stride_B =
ALIGN_POT(mip_extent_el.width * fmt_blksize_B, align_mask + 1);
}
uint64_t surf_stride_B =
(uint64_t)slayout->tiled_or_linear.row_stride_B * mip_extent_el.height;
surf_stride_B = ALIGN_POT(surf_stride_B, (uint64_t)align_mask + 1);
slayout->tiled_or_linear.surface_stride_B = surf_stride_B;
slayout->size_B = surf_stride_B * mip_extent_el.depth * props->nr_samples;
return true;
}
#define pan_mod_linear_emit_tex_payload_entry \
GENX(pan_tex_emit_linear_payload_entry)
#define pan_mod_linear_emit_color_attachment \
GENX(pan_emit_linear_color_attachment)
#define pan_mod_linear_emit_zs_attachment GENX(pan_emit_linear_zs_attachment)
#define pan_mod_linear_emit_s_attachment GENX(pan_emit_linear_s_attachment)
#if PAN_ARCH >= 5
#define EMIT_ATT(__name) \
.emit_color_attachment = pan_mod_##__name##_emit_color_attachment, \
.emit_zs_attachment = pan_mod_##__name##_emit_zs_attachment, \
.emit_s_attachment = pan_mod_##__name##_emit_s_attachment
#else
#define EMIT_ATT(__name) \
.emit_color_attachment = NULL, .emit_zs_attachment = NULL, \
.emit_s_attachment = NULL
#endif
#define PAN_MOD_DEF(__name) \
{ \
.match = pan_mod_##__name##_match, \
.test_props = pan_mod_##__name##_test_props, \
.get_wsi_row_pitch = pan_mod_##__name##_get_wsi_row_pitch, \
.init_plane_layout = pan_mod_##__name##_init_plane_layout, \
.init_slice_layout = pan_mod_##__name##_init_slice_layout, \
.emit_tex_payload_entry = pan_mod_##__name##_emit_tex_payload_entry, \
EMIT_ATT(__name), \
}
static const struct pan_mod_handler pan_mod_handlers[] = {
PAN_MOD_DEF(afbc),
PAN_MOD_DEF(u_tiled),
PAN_MOD_DEF(linear),
#if PAN_ARCH >= 10
PAN_MOD_DEF(interleaved_64k),
PAN_MOD_DEF(afrc),
#endif
};
const struct pan_mod_handler *
GENX(pan_mod_get_handler)(uint64_t modifier)
{
for (unsigned i = 0; i < ARRAY_SIZE(pan_mod_handlers); i++) {
if (pan_mod_handlers[i].match(modifier))
return &pan_mod_handlers[i];
}
return NULL;
}
+121
View File
@@ -0,0 +1,121 @@
/*
* Copyright (C) 2025 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#ifndef __PAN_MOD_H
#define __PAN_MOD_H
#ifdef __cplusplus
extern "C" {
#endif
#include "util/format/u_format.h"
#include "pan_layout.h"
struct pan_attachment_info;
struct pan_image;
struct pan_image_view;
struct pan_image_usage;
struct pan_kmod_dev_props;
struct pan_mod_handler;
enum pan_mod_support {
PAN_MOD_NOT_SUPPORTED = 0,
PAN_MOD_NOT_OPTIMAL,
PAN_MOD_OPTIMAL,
};
struct pan_mod_handler {
bool (*match)(uint64_t mod);
/* Used to check if a set of image properties is valid. Passing a NULL iusage
* is valid and means "optimal set of usage for this mod". This implies
* that some non-supported cases can't be detected or can be reported as
* optimal when specific usage flags would report it non-optimal.
*/
enum pan_mod_support (*test_props)(const struct pan_kmod_dev_props *dprops,
const struct pan_image_props *iprops,
const struct pan_image_usage *iusage);
/* Optional method used to initialize modifier-specific per-plane layout
* data. This is called before init_slice_layout().
*/
bool (*init_plane_layout)(const struct pan_image_props *props,
unsigned plane_idx,
struct pan_image_layout *plane_layout);
bool (*init_slice_layout)(
const struct pan_image_props *props, unsigned plane_idx,
struct pan_image_extent mip_extent_px,
const struct pan_image_layout_constraints *layout_constraints,
struct pan_image_slice_layout *slice);
uint32_t (*get_wsi_row_pitch)(const struct pan_image *image,
unsigned plane_idx, unsigned mip_level);
void (*emit_tex_payload_entry)(const struct pan_image_view *iview,
unsigned mip_level, unsigned layer_or_z_slice,
unsigned sample, void **payload);
void (*emit_color_attachment)(const struct pan_attachment_info *att,
void *payload);
void (*emit_zs_attachment)(const struct pan_attachment_info *att,
void *payload);
void (*emit_s_attachment)(const struct pan_attachment_info *att,
void *payload);
};
#ifdef PAN_ARCH
const struct pan_mod_handler *GENX(pan_mod_get_handler)(uint64_t modifier);
static inline const struct pan_mod_handler *
pan_mod_get_handler(unsigned arch, uint64_t modifier)
{
assert(arch == PAN_ARCH);
return GENX(pan_mod_get_handler)(modifier);
}
#else
const struct pan_mod_handler *pan_mod_get_handler_v4(uint64_t modifier);
const struct pan_mod_handler *pan_mod_get_handler_v5(uint64_t modifier);
const struct pan_mod_handler *pan_mod_get_handler_v6(uint64_t modifier);
const struct pan_mod_handler *pan_mod_get_handler_v7(uint64_t modifier);
const struct pan_mod_handler *pan_mod_get_handler_v9(uint64_t modifier);
const struct pan_mod_handler *pan_mod_get_handler_v10(uint64_t modifier);
const struct pan_mod_handler *pan_mod_get_handler_v12(uint64_t modifier);
const struct pan_mod_handler *pan_mod_get_handler_v13(uint64_t modifier);
const struct pan_mod_handler *pan_mod_get_handler_v14(uint64_t modifier);
static inline const struct pan_mod_handler *
pan_mod_get_handler(unsigned arch, uint64_t modifier)
{
switch (arch) {
case 4:
return pan_mod_get_handler_v4(modifier);
case 5:
return pan_mod_get_handler_v5(modifier);
case 6:
return pan_mod_get_handler_v6(modifier);
case 7:
return pan_mod_get_handler_v7(modifier);
case 9:
return pan_mod_get_handler_v9(modifier);
case 10:
return pan_mod_get_handler_v10(modifier);
case 12:
return pan_mod_get_handler_v12(modifier);
case 13:
return pan_mod_get_handler_v13(modifier);
case 14:
return pan_mod_get_handler_v14(modifier);
default:
UNREACHABLE("Unsupported arch");
}
}
#endif
#ifdef __cplusplus
} /* extern C */
#endif
#endif
+118
View File
@@ -0,0 +1,118 @@
/*
* © Copyright 2017-2018 Alyssa Rosenzweig
* SPDX-License-Identifier: MIT
*/
#ifndef __PAN_POOL_H__
#define __PAN_POOL_H__
#include <stddef.h>
#include <genxml/gen_macros.h>
#include "util/u_dynarray.h"
struct pan_ptr {
/* CPU address */
void *cpu;
/* GPU address */
uint64_t gpu;
};
static inline struct pan_ptr
pan_ptr_offset(struct pan_ptr ptr, int64_t offset)
{
ptr.cpu = ((char *)ptr.cpu) + offset;
ptr.gpu += offset;
return ptr;
}
/* Represents grow-only memory. */
struct pan_pool {
/* Minimum size for allocated BOs. */
size_t slab_size;
};
static inline void
pan_pool_init(struct pan_pool *pool, size_t slab_size)
{
pool->slab_size = slab_size;
}
/* Represents a fat pointer for GPU-mapped memory, returned from the transient
* allocator and not used for much else */
struct pan_ptr pan_pool_alloc_aligned(struct pan_pool *pool, size_t sz,
unsigned alignment);
#define PAN_POOL_ALLOCATOR(pool_subclass, alloc_func) \
struct pan_ptr pan_pool_alloc_aligned(struct pan_pool *p, size_t sz, \
unsigned alignment) \
{ \
pool_subclass *pool = container_of(p, pool_subclass, base); \
return alloc_func(pool, sz, alignment); \
}
static inline uint64_t
pan_pool_upload_aligned(struct pan_pool *pool, const void *data, size_t sz,
unsigned alignment)
{
struct pan_ptr transfer = pan_pool_alloc_aligned(pool, sz, alignment);
if (transfer.cpu)
memcpy(transfer.cpu, data, sz);
return transfer.gpu;
}
static inline uint64_t
pan_pool_upload(struct pan_pool *pool, const void *data, size_t sz)
{
return pan_pool_upload_aligned(pool, data, sz, sz);
}
struct pan_desc_alloc_info {
unsigned size;
unsigned align;
unsigned nelems;
};
#define PAN_DESC_ARRAY(count, name) \
{ \
.size = pan_size(name), .align = pan_alignment(name), .nelems = count, \
}
#define PAN_DESC(name) PAN_DESC_ARRAY(1, name)
#define PAN_DESC_AGGREGATE(...) \
(struct pan_desc_alloc_info[]) \
{ \
__VA_ARGS__, {0}, \
}
static inline struct pan_ptr
pan_pool_alloc_descs(struct pan_pool *pool,
const struct pan_desc_alloc_info *descs)
{
unsigned size = 0;
unsigned align = descs[0].align;
for (unsigned i = 0; descs[i].size; i++) {
assert(!(size & (descs[i].align - 1)));
size += descs[i].size * descs[i].nelems;
}
return pan_pool_alloc_aligned(pool, size, align);
}
#define pan_pool_alloc_desc(pool, name) \
pan_pool_alloc_descs(pool, PAN_DESC_AGGREGATE(PAN_DESC(name)))
#define pan_pool_alloc_desc_array(pool, count, name) \
pan_pool_alloc_descs(pool, PAN_DESC_AGGREGATE(PAN_DESC_ARRAY(count, name)))
#define pan_pool_alloc_desc_aggregate(pool, ...) \
pan_pool_alloc_descs(pool, PAN_DESC_AGGREGATE(__VA_ARGS__))
#endif
+162
View File
@@ -0,0 +1,162 @@
/*
* Copyright (C) 2019 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#include "util/macros.h"
#include "kmod/pan_kmod.h"
#include "pan_props.h"
#include <genxml/gen_macros.h>
unsigned
pan_query_l2_slices(const struct pan_kmod_dev_props *props)
{
/* L2_SLICES is MEM_FEATURES[11:8] minus(1) */
return ((props->mem_features >> 8) & 0xF) + 1;
}
struct pan_tiler_features
pan_query_tiler_features(const struct pan_kmod_dev_props *props)
{
/* Default value (2^9 bytes and 8 levels) to match old behaviour */
uint32_t raw = props->tiler_features;
/* Bin size is log2 in the first byte, max levels in the second byte */
return (struct pan_tiler_features){
.bin_size = (1 << (raw & BITFIELD_MASK(5))),
.max_levels = (raw >> 8) & BITFIELD_MASK(4),
};
}
unsigned
pan_query_core_count(const struct pan_kmod_dev_props *props,
unsigned *core_id_range)
{
/* On older kernels, worst-case to 16 cores */
unsigned mask = props->shader_present;
/* Some cores might be absent. In some cases, we care
* about the range of core IDs (that is, the greatest core ID + 1). If
* the core mask is contiguous, this equals the core count.
*/
*core_id_range = util_last_bit(mask);
/* The actual core count skips overs the gaps */
return util_bitcount(mask);
}
unsigned
pan_query_thread_tls_alloc(const struct pan_kmod_dev_props *props)
{
return props->max_tls_instance_per_core ?: props->max_threads_per_core;
}
unsigned
pan_compute_max_thread_count(const struct pan_kmod_dev_props *props,
unsigned work_reg_count)
{
unsigned aligned_reg_count;
/* 4, 8 or 16 registers per shader on Midgard
* 32 or 64 registers per shader on Bifrost
*/
if (pan_arch(props->gpu_id) <= 5) {
aligned_reg_count = util_next_power_of_two(MAX2(work_reg_count, 4));
assert(aligned_reg_count <= 16);
} else {
aligned_reg_count = work_reg_count <= 32 ? 32 : 64;
}
return MIN3(props->max_threads_per_wg, props->max_threads_per_core,
props->num_registers_per_core / aligned_reg_count);
}
uint32_t
pan_query_compressed_formats(const struct pan_kmod_dev_props *props)
{
return props->texture_features[0];
}
/* Check for AFBC hardware support. AFBC is introduced in v5. Implementations
* may omit it, signaled as a nonzero value in the AFBC_FEATURES property. */
bool
pan_query_afbc(const struct pan_kmod_dev_props *props)
{
unsigned reg = props->afbc_features;
return (pan_arch(props->gpu_id) >= 5) && (reg == 0);
}
/* Check for AFRC hardware support. AFRC is introduced in v10. Implementations
* may omit it, signaled in bit 25 of TEXTURE_FEATURES_0 property. */
bool
pan_query_afrc(const struct pan_kmod_dev_props *props)
{
return (pan_arch(props->gpu_id) >= 10) &&
(props->texture_features[0] & (1 << 25));
}
/*
* To pipeline multiple tiles, a given tile may use at most half of the tile
* buffer. This function returns the optimal size (assuming pipelining).
*
* For Mali-G510 and Mali-G310, we will need extra logic to query the tilebuffer
* size for the particular variant. The CORE_FEATURES register might help.
*/
unsigned
pan_query_tib_size(const struct pan_model *model)
{
/* Preconditions ensure the returned value is a multiple of 1 KiB, the
* granularity of the colour buffer allocation field.
*/
assert(model->tilebuffer.color_size >= 2048);
assert(util_is_power_of_two_nonzero(model->tilebuffer.color_size));
return model->tilebuffer.color_size;
}
unsigned
pan_query_z_tib_size(const struct pan_model *model)
{
/* Preconditions ensure the returned value is a multiple of 1 KiB, the
* granularity of the colour buffer allocation field.
*/
assert(model->tilebuffer.z_size >= 1024);
assert(util_is_power_of_two_nonzero(model->tilebuffer.z_size));
return model->tilebuffer.z_size;
}
uint64_t
pan_clamp_to_usable_va_range(const struct pan_kmod_dev *dev, uint64_t va)
{
struct pan_kmod_va_range user_va_range =
pan_kmod_dev_query_user_va_range(dev);
if (va < user_va_range.start)
return user_va_range.start;
else if (va > user_va_range.start + user_va_range.size)
return user_va_range.start + user_va_range.size;
return va;
}
uint64_t
pan_choose_gpu_va_alignment(const struct pan_kmod_vm *vm, uint64_t size)
{
assert(vm->pgsize_bitmap != 0);
uint64_t align = 0;
u_foreach_bit64(pgsize_bit, vm->pgsize_bitmap) {
uint64_t pgsize = (uint64_t)1 << pgsize_bit;
if (align > 0 && pgsize > size)
break;
align = pgsize;
}
return align;
}
+132
View File
@@ -0,0 +1,132 @@
/*
* Copyright (C) 2019 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#ifndef PAN_PROPS_H
#define PAN_PROPS_H
#include <stdbool.h>
#include <stdint.h>
#include "util/macros.h"
#include "pan_model.h"
struct pan_kmod_dev;
struct pan_kmod_dev_props;
struct pan_kmod_vm;
#define ARM_VENDOR_ID 0x13B5
unsigned pan_query_l2_slices(const struct pan_kmod_dev_props *props);
struct pan_tiler_features
pan_query_tiler_features(const struct pan_kmod_dev_props *props);
unsigned pan_query_thread_tls_alloc(const struct pan_kmod_dev_props *props);
uint32_t pan_query_compressed_formats(const struct pan_kmod_dev_props *props);
unsigned pan_query_core_count(const struct pan_kmod_dev_props *props,
unsigned *core_id_range);
bool pan_query_afbc(const struct pan_kmod_dev_props *props);
bool pan_query_afrc(const struct pan_kmod_dev_props *props);
unsigned pan_query_tib_size(const struct pan_model *model);
unsigned pan_query_z_tib_size(const struct pan_model *model);
static inline unsigned
pan_query_optimal_tib_size(unsigned arch, const struct pan_model *model)
{
unsigned tib_size = pan_query_tib_size(model);
/* On V5, as well as V7 and later, we can disable pipelining to gain some
* extra tib memory.
*/
if (arch > 4 && arch != 6)
return tib_size / 2;
return tib_size;
}
static inline unsigned
pan_query_optimal_z_tib_size(unsigned arch, const struct pan_model *model)
{
unsigned tib_size = pan_query_z_tib_size(model);
/* On V5, as well as V7 and later, we can disable pipelining to gain some
* extra tib memory.
*/
if (arch > 4 && arch != 6)
return tib_size / 2;
return tib_size;
}
uint64_t pan_clamp_to_usable_va_range(const struct pan_kmod_dev *dev,
uint64_t va);
uint64_t pan_choose_gpu_va_alignment(const struct pan_kmod_vm *vm,
uint64_t size);
unsigned pan_compute_max_thread_count(const struct pan_kmod_dev_props *props,
unsigned work_reg_count);
static inline unsigned
pan_max_effective_tile_size(unsigned arch)
{
if (arch >= 12)
return 64 * 64;
if (arch >= 10)
return 32 * 32;
return 16 * 16;
}
static inline unsigned
pan_meta_tile_size(unsigned arch)
{
if (arch >= 12)
return 64;
return 32;
}
static inline uint32_t
pan_get_max_cbufs(unsigned arch, unsigned max_tib_size)
{
if (arch < 5)
return 1;
const unsigned min_msaa = 4; /* Vulkan *requires* at least 4x MSAA support */
const unsigned max_cbuf_format = 4 * 4; /* R32G32B32A32 */
const unsigned min_tile_size = 4 * 4;
unsigned max_cbufs =
max_tib_size / (min_msaa * max_cbuf_format * min_tile_size);
return MIN2(max_cbufs, 8);
}
static inline unsigned
pan_get_max_msaa(unsigned arch, unsigned max_tib_size, unsigned max_cbuf_atts,
unsigned format_size)
{
if (arch < 5)
return 8;
assert(max_cbuf_atts > 0);
assert(format_size > 0);
const unsigned min_tile_size = 4 * 4;
unsigned max_msaa = max_tib_size / (max_cbuf_atts * format_size *
min_tile_size);
return MIN2(max_msaa, 16);
}
#endif
+139
View File
@@ -0,0 +1,139 @@
/*
* Copyright (C) 2019 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#include <stdint.h>
#include "pan_samples.h"
/* Sample positions are specified partially in hardware, partially in software
* on Mali. On Midgard, sample positions are completely fixed but need to be
* software accessible to implement gl_SamplePosition. On Bifrost, sample
* positions are part fixed, part programmable, and again need to be accessible
* for gl_SamplePosition. The mali_sample_position data structure is
* hardware-defined on Bifrost as a packed 8:8 fixed point format. The
* mali_sample_positions array is part-hardware, part-software-defined: it must
* give the sample position when indexed by the sample index, as well as
* reading back the origin when indexed by 32.
*
* The upshot is all of this is known at compile-time, so we can just hardcode
* the LUT, upload it to GPU memory on device initialization, and forget about
* it.
*/
struct mali_sample_position {
uint16_t x, y;
} __attribute__((packed));
struct mali_sample_positions {
struct mali_sample_position positions[32];
struct mali_sample_position origin;
struct mali_sample_position padding[64 - (32 + 1)];
} __attribute__((packed));
/* SAMPLE16 constructs a single sample in terms of 1/16's of the grid, centered
* at the origin. SAMPLE4/8 swap the units for legibility. */
#define SAMPLE16(x, y) \
{ \
(((x) + 8) * (256 / 16)), (((y) + 8) * (256 / 16)) \
}
#define SAMPLE8(x, y) SAMPLE16((x)*2, (y)*2)
#define SAMPLE4(x, y) SAMPLE16((x)*4, (y)*4)
/* clang-format off */
const struct mali_sample_positions sample_position_lut[] = {
[MALI_SAMPLE_PATTERN_SINGLE_SAMPLED] = {
.positions = {
SAMPLE4(0, 0)
},
.origin = SAMPLE4(0, 0)
},
[MALI_SAMPLE_PATTERN_ORDERED_4X_GRID] = {
.positions = {
SAMPLE4(-1, -1),
SAMPLE4( 1, -1),
SAMPLE4(-1, 1),
SAMPLE4( 1, 1),
},
.origin = SAMPLE4(0, 0)
},
[MALI_SAMPLE_PATTERN_ROTATED_4X_GRID] = {
.positions = {
SAMPLE8(-1, -3),
SAMPLE8( 3, -1),
SAMPLE8(-3, 1),
SAMPLE8( 1, 3),
},
.origin = SAMPLE8(0, 0)
},
[MALI_SAMPLE_PATTERN_D3D_8X_GRID] = {
.positions = {
SAMPLE16( 1, -3),
SAMPLE16(-1, 3),
SAMPLE16( 5, 1),
SAMPLE16(-3, -5),
SAMPLE16(-5, 5),
SAMPLE16(-7, -1),
SAMPLE16( 3, 7),
SAMPLE16( 7, -7),
},
.origin = SAMPLE16(0, 0)
},
[MALI_SAMPLE_PATTERN_D3D_16X_GRID] = {
.positions = {
SAMPLE16( 1, 1),
SAMPLE16(-1, -3),
SAMPLE16(-3, 2),
SAMPLE16( 4, -1),
SAMPLE16(-5, -2),
SAMPLE16( 2, 5),
SAMPLE16( 5, 3),
SAMPLE16( 3, -5),
SAMPLE16(-2, 6),
SAMPLE16( 0, -7),
SAMPLE16(-4, -6),
SAMPLE16(-6, 4),
SAMPLE16(-8, 0),
SAMPLE16( 7, -4),
SAMPLE16( 6, 7),
SAMPLE16(-7, -8),
},
.origin = SAMPLE16(0, 0)
},
[MALI_SAMPLE_PATTERN_ROTATED_2X_GRID] = {
.positions = {
SAMPLE4( 1, 1),
SAMPLE4(-1, -1),
},
.origin = SAMPLE4(0, 0)
}
};
/* clang-format on */
unsigned
pan_sample_positions_buffer_size(void)
{
return sizeof(sample_position_lut);
}
unsigned
pan_sample_positions_offset(enum mali_sample_pattern pattern)
{
assert(pattern < ARRAY_SIZE(sample_position_lut));
unsigned offset = (pattern * sizeof(sample_position_lut[0]));
return offset;
}
void
pan_upload_sample_positions(void *buffer)
{
memcpy(buffer, sample_position_lut, sizeof(sample_position_lut));
}
+17
View File
@@ -0,0 +1,17 @@
/*
* Copyright (C) 2019 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#ifndef PAN_SAMPLES_H
#define PAN_SAMPLES_H
#include <genxml/gen_macros.h>
unsigned pan_sample_positions_buffer_size(void);
void pan_upload_sample_positions(void *buffer);
unsigned pan_sample_positions_offset(enum mali_sample_pattern pattern);
#endif
+65
View File
@@ -0,0 +1,65 @@
/*
* Copyright (C) 2019 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#include "util/macros.h"
#include "util/u_math.h"
#include "pan_encoder.h"
/* Midgard has a small register file, so shaders with high register pressure
* need to spill from the register file onto the stack. In addition to
* spilling, it is desireable to allocate temporary arrays on the stack (for
* instance because the register file does not support indirect access but the
* stack does).
*
* The stack is located in "Thread Local Storage", sometimes abbreviated TLS in
* the kernel source code. Thread local storage is allocated per-thread,
* per-core, so threads executing concurrently do not interfere with each
* other's stacks. On modern kernels, we may query
* DRM_PANFROST_PARAM_THREAD_TLS_ALLOC for the number of threads per core we
* must allocate for, and DRM_PANFROST_PARAM_SHADER_PRESENT for a bitmask of
* shader cores (so take a popcount of that mask for the number of shader
* cores). On older kernels that do not support querying these values,
* following kbase, we may use the worst-case value of 256 threads for
* THREAD_TLS_ALLOC, and the worst-case value of 16 cores for Midgard per the
* "shader core count" column of the implementations table in
* https://en.wikipedia.org/wiki/Mali_%28GPU% [citation needed]
*
* Within a particular thread, there is stack allocated. If it is present, its
* size is a power-of-two, and it is at least 16 bytes. Stack is allocated
* with the shared memory descriptor used for all shaders within a frame (note
* that they don't execute concurrently so it's fine). So, consider the maximum
* stack size used by any shader within a job, and then compute (where npot
* denotes the next power of two):
*
* bytes/thread = npot(max(size, 16))
* allocated = (# of bytes/thread) * (# of threads/core) * (# of cores)
*
* The size of Thread Local Storage is signaled to the GPU in the tls_size
* field, which has a log2 modifier and is in units of 16 bytes.
*/
/* Computes log_stack_size = log2(ceil(s / 16)) */
unsigned
pan_get_stack_shift(unsigned stack_size)
{
if (stack_size)
return util_logbase2_ceil(DIV_ROUND_UP(stack_size, 16));
else
return 0;
}
/* Computes the aligned stack size given the shift and thread count. */
unsigned
pan_get_total_stack_size(unsigned thread_size, unsigned threads_per_core,
unsigned core_id_range)
{
unsigned size_per_thread =
(thread_size == 0) ? 0
: util_next_power_of_two(ALIGN_POT(thread_size, 16));
return size_per_thread * threads_per_core * core_id_range;
}
+217
View File
@@ -0,0 +1,217 @@
/*
* Copyright (C) 2021 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#ifndef __PAN_SHADER_H__
#define __PAN_SHADER_H__
#include "compiler/nir/nir.h"
#include "genxml/gen_macros.h"
#include "panfrost/lib/pan_props.h"
#include "panfrost/compiler/pan_compiler.h"
#ifdef PAN_ARCH
#if PAN_ARCH >= 9
static inline enum mali_shader_stage
pan_shader_stage(const struct pan_shader_info *info)
{
switch (info->stage) {
case MESA_SHADER_VERTEX:
return MALI_SHADER_STAGE_VERTEX;
case MESA_SHADER_FRAGMENT:
return MALI_SHADER_STAGE_FRAGMENT;
default:
return MALI_SHADER_STAGE_COMPUTE;
}
}
#endif
#if PAN_ARCH >= 7
static inline enum mali_shader_register_allocation
pan_register_allocation(unsigned work_reg_count)
{
return (work_reg_count <= 32)
? MALI_SHADER_REGISTER_ALLOCATION_32_PER_THREAD
: MALI_SHADER_REGISTER_ALLOCATION_64_PER_THREAD;
}
#endif
static inline enum mali_depth_source
pan_depth_source(const struct pan_shader_info *info)
{
return info->fs.writes_depth ? MALI_DEPTH_SOURCE_SHADER
: MALI_DEPTH_SOURCE_FIXED_FUNCTION;
}
#if PAN_ARCH < 9
#if PAN_ARCH <= 5
static inline void
pan_shader_prepare_midgard_rsd(const struct pan_shader_info *info,
struct MALI_RENDERER_STATE *rsd)
{
assert((info->push.count & 3) == 0);
rsd->properties.uniform_count = info->push.count / 4;
rsd->properties.shader_has_side_effects = info->writes_global;
rsd->properties.fp_mode = MALI_FP_MODE_GL_INF_NAN_ALLOWED;
/* For fragment shaders, work register count, early-z, reads at draw-time */
if (info->stage != MESA_SHADER_FRAGMENT) {
rsd->properties.work_register_count = info->work_reg_count;
} else {
rsd->properties.shader_reads_tilebuffer = info->fs.outputs_read;
/* However, forcing early-z in the shader overrides draw-time */
rsd->properties.force_early_z = info->fs.early_fragment_tests;
}
}
#else
#define pan_preloads(reg) (preload & BITFIELD64_BIT(reg))
static void
pan_make_preload(mesa_shader_stage stage, uint64_t preload,
struct MALI_PRELOAD *out)
{
switch (stage) {
case MESA_SHADER_VERTEX:
out->vertex.position_result_address_lo = pan_preloads(58);
out->vertex.position_result_address_hi = pan_preloads(59);
out->vertex.vertex_id = pan_preloads(61);
out->vertex.instance_id = pan_preloads(62);
break;
case MESA_SHADER_FRAGMENT:
out->fragment.primitive_id = pan_preloads(57);
out->fragment.primitive_flags = pan_preloads(58);
out->fragment.fragment_position = pan_preloads(59);
out->fragment.sample_mask_id = pan_preloads(61);
out->fragment.coverage = true;
break;
default:
out->compute.local_invocation_xy = pan_preloads(55);
out->compute.local_invocation_z = pan_preloads(56);
out->compute.work_group_x = pan_preloads(57);
out->compute.work_group_y = pan_preloads(58);
out->compute.work_group_z = pan_preloads(59);
out->compute.global_invocation_x = pan_preloads(60);
out->compute.global_invocation_y = pan_preloads(61);
out->compute.global_invocation_z = pan_preloads(62);
break;
}
}
#if PAN_ARCH == 7
static inline void
pan_pack_message_preload(struct MALI_MESSAGE_PRELOAD *cfg,
const struct bifrost_message_preload *msg)
{
enum mali_message_preload_register_format regfmt =
msg->fp16 ? MALI_MESSAGE_PRELOAD_REGISTER_FORMAT_F16
: MALI_MESSAGE_PRELOAD_REGISTER_FORMAT_F32;
if (msg->enabled && msg->texture) {
cfg->type = MALI_MESSAGE_TYPE_VAR_TEX;
cfg->var_tex.varying_index = msg->varying_index;
cfg->var_tex.texture_index = msg->texture_index;
cfg->var_tex.register_format = regfmt;
cfg->var_tex.skip = msg->skip;
cfg->var_tex.zero_lod = msg->zero_lod;
} else if (msg->enabled) {
cfg->type = MALI_MESSAGE_TYPE_LD_VAR;
cfg->ld_var.varying_index = msg->varying_index;
cfg->ld_var.register_format = regfmt;
cfg->ld_var.num_components = msg->num_components;
} else {
cfg->type = MALI_MESSAGE_TYPE_DISABLED;
}
}
#endif
static inline void
pan_shader_prepare_bifrost_rsd(const struct pan_shader_info *info,
struct MALI_RENDERER_STATE *rsd)
{
unsigned fau_count = DIV_ROUND_UP(info->push.count, 2);
rsd->preload.uniform_count = fau_count;
#if PAN_ARCH >= 7
rsd->properties.shader_register_allocation =
pan_register_allocation(info->work_reg_count);
#endif
pan_make_preload(info->stage, info->preload, &rsd->preload);
if (info->stage == MESA_SHADER_FRAGMENT) {
rsd->properties.shader_modifies_coverage =
info->fs.writes_coverage || info->fs.can_discard;
rsd->properties.allow_forward_pixel_to_be_killed = !info->writes_global;
#if PAN_ARCH >= 7
rsd->properties.shader_wait_dependency_6 = info->bifrost.wait_6;
rsd->properties.shader_wait_dependency_7 = info->bifrost.wait_7;
pan_pack_message_preload(&rsd->message_preload_1,
&info->bifrost.messages[0]);
pan_pack_message_preload(&rsd->message_preload_2,
&info->bifrost.messages[1]);
#endif
} else if (info->stage == MESA_SHADER_VERTEX && info->vs.secondary_enable) {
rsd->secondary_preload.uniform_count = fau_count;
pan_make_preload(info->stage, info->vs.secondary_preload,
&rsd->secondary_preload);
rsd->secondary_shader = rsd->shader.shader + info->vs.secondary_offset;
#if PAN_ARCH >= 7
rsd->properties.secondary_shader_register_allocation =
pan_register_allocation(info->vs.secondary_work_reg_count);
#endif
}
}
#endif
static inline void
pan_shader_prepare_rsd(const struct pan_shader_info *shader_info,
uint64_t shader_ptr, struct MALI_RENDERER_STATE *rsd)
{
#if PAN_ARCH <= 5
shader_ptr |= shader_info->midgard.first_tag;
#endif
rsd->shader.shader = shader_ptr;
rsd->shader.attribute_count = shader_info->attribute_count;
rsd->shader.varying_count = shader_info->varyings.formats.count;
rsd->shader.texture_count = shader_info->texture_count;
rsd->shader.sampler_count = shader_info->sampler_count;
rsd->properties.shader_contains_barrier = shader_info->contains_barrier;
rsd->properties.uniform_buffer_count = shader_info->ubo_count;
if (shader_info->stage == MESA_SHADER_FRAGMENT) {
rsd->properties.stencil_from_shader = shader_info->fs.writes_stencil;
rsd->properties.depth_source = pan_depth_source(shader_info);
/* This also needs to be set if the API forces per-sample
* shading, but that'll just got ORed in */
rsd->multisample_misc.evaluate_per_sample =
shader_info->fs.sample_shading;
}
#if PAN_ARCH >= 6
pan_shader_prepare_bifrost_rsd(shader_info, rsd);
#else
pan_shader_prepare_midgard_rsd(shader_info, rsd);
#endif
}
#endif /* PAN_ARCH */
#endif
#endif
File diff suppressed because it is too large. Load diff
+79
View File
@@ -0,0 +1,79 @@
/*
* Copyright (C) 2008 VMware, Inc.
* Copyright (C) 2014 Broadcom
* Copyright (C) 2018-2019 Alyssa Rosenzweig
* Copyright (C) 2019-2020 Collabora, Ltd.
* Copyright (C) 2025 Arm Ltd.
* SPDX-License-Identifier: MIT
*/
#ifndef __PAN_TEXTURE_H
#define __PAN_TEXTURE_H
#ifndef PAN_ARCH
#error "PAN_ARCH must be defined"
#endif
#include "pan_image.h"
struct pan_ptr;
struct mali_texture_packed;
#if PAN_ARCH >= 7
void GENX(pan_texture_swizzle_replicate_x)(struct pan_image_view *iview);
#endif
#if PAN_ARCH == 7
void GENX(pan_texture_afbc_reswizzle)(struct pan_image_view *iview);
#endif
unsigned
GENX(pan_texture_estimate_payload_size)(const struct pan_image_view *iview);
void GENX(pan_sampled_texture_emit)(const struct pan_image_view *iview,
struct mali_texture_packed *out,
const struct pan_ptr *payload);
void GENX(pan_tex_emit_linear_payload_entry)(const struct pan_image_view *iview,
unsigned mip_level,
unsigned layer_or_z_slice,
unsigned sample, void **payload);
void GENX(pan_tex_emit_u_tiled_payload_entry)(
const struct pan_image_view *iview, unsigned mip_level,
unsigned layer_or_z_slice, unsigned sample, void **payload);
void
GENX(pan_tex_emit_afbc_payload_entry)(const struct pan_image_view *iview,
unsigned mip_level, unsigned layer_or_z_slice,
unsigned sample, void **payload);
#if PAN_ARCH >= 9
void GENX(pan_storage_texture_emit)(const struct pan_image_view *iview,
struct mali_texture_packed *out,
const struct pan_ptr *payload);
#endif
void GENX(pan_tex_emit_linear_payload_entry)(const struct pan_image_view *iview,
unsigned mip_level,
unsigned layer_or_z_slice,
unsigned sample, void **payload);
void GENX(pan_tex_emit_u_tiled_payload_entry)(
const struct pan_image_view *iview, unsigned mip_level,
unsigned layer_or_z_slice, unsigned sample, void **payload);
void GENX(pan_tex_emit_afbc_payload_entry)(const struct pan_image_view *iview,
unsigned mip_level,
unsigned layer_or_z_slice,
unsigned sample, void **payload);
#if PAN_ARCH >= 10
void GENX(pan_tex_emit_interleaved_64k_payload_entry)(
const struct pan_image_view *iview, unsigned mip_level,
unsigned layer_or_z_slice, unsigned sample, void **payload);
void GENX(pan_tex_emit_afrc_payload_entry)(
const struct pan_image_view *iview, unsigned mip_level,
unsigned layer_or_z_slice, unsigned sample, void **payload);
#endif
#endif
+352
View File
@@ -0,0 +1,352 @@
/*
* Copyright (C) 2019 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#include "util/macros.h"
#include "util/u_math.h"
#include "pan_encoder.h"
/* Mali GPUs are tiled-mode renderers, rather than immediate-mode.
* Conceptually, the screen is divided into 16x16 tiles. Vertex shaders run.
* Then, a fixed-function hardware block (the tiler) consumes the gl_Position
* results. For each triangle specified, it marks each containing tile as
* containing that triangle. This set of "triangles per tile" form the "polygon
* list". Finally, the rasterization unit consumes the polygon list to invoke
* the fragment shader.
*
* In practice, it's a bit more complicated than this. On Midgard chips with an
* "advanced tiling unit" (all except T720/T820/T830), 16x16 is the logical
* tile size, but Midgard features "hierarchical tiling", where power-of-two
* multiples of the base tile size can be used: hierarchy level 0 (16x16),
* level 1 (32x32), level 2 (64x64), per public information about Midgard's
* tiling. In fact, tiling goes up to 4096x4096 (!), although in practice
* 128x128 is the largest usually used (though higher modes are enabled). The
* idea behind hierarchical tiling is to use low tiling levels for small
* triangles and high levels for large triangles, to minimize memory bandwidth
* and repeated fragment shader invocations (the former issue inherent to
* immediate-mode rendering and the latter common in traditional tilers).
*
* The tiler itself works by reading varyings in and writing a polygon list
* out. Unfortunately (for us), both of these buffers are managed in main
* memory; although they ideally will be cached, it is the drivers'
* responsibility to allocate these buffers. Varying buffer allocation is
* handled elsewhere, as it is not tiler specific; the real issue is allocating
* the polygon list.
*
* This is hard, because from the driver's perspective, we have no information
* about what geometry will actually look like on screen; that information is
* only gained from running the vertex shader. (Theoretically, we could run the
* vertex shaders in software as a prepass, or in hardware with transform
* feedback as a prepass, but either idea is ludicrous on so many levels).
*
* Instead, Mali uses a bit of a hybrid approach, splitting the polygon list
* into three distinct pieces. First, the driver statically determines which
* tile hierarchy levels to use (more on that later). At this point, we know the
* framebuffer dimensions and all the possible tilings of the framebuffer, so
* we know exactly how many tiles exist across all hierarchy levels. The first
* piece of the polygon list is the header, which is exactly 8 bytes per tile,
* plus padding and a small 64-byte prologue. (If that doesn't remind you of
* AFBC, it should. See pan_afbc.c for some fun parallels). The next part is
* the polygon list body, which seems to contain 512 bytes per tile, again
* across every level of the hierarchy. These two parts form the polygon list
* buffer. This buffer has a statically determinable size, approximately equal
* to the # of tiles across all hierarchy levels * (8 bytes + 512 bytes), plus
* alignment / minimum restrictions / etc.
*
* The third piece is the easy one (for us): the tiler heap. In essence, the
* tiler heap is a gigantic slab that's as big as could possibly be necessary
* in the worst case imaginable. Just... a gigantic allocation that we give a
* start and end pointer to. What's the catch? The tiler heap is lazily
* allocated; that is, a huge amount of memory is _reserved_, but only a tiny
* bit is actually allocated upfront. The GPU just keeps using the
* unallocated-but-reserved portions as it goes along, generating page faults
* if it goes beyond the allocation, and then the kernel is instructed to
* expand the allocation on page fault (known in the vendor kernel as growable
* memory). This is quite a bit of bookkeeping of its own, but that task is
* pushed to kernel space and we can mostly ignore it here, just remembering to
* set the GROWABLE flag so the kernel actually uses this path rather than
* allocating a gigantic amount up front and burning a hole in RAM.
*
* As far as determining which hierarchy levels to use, the simple answer is
* that right now, we don't. In the tiler configuration fields (consistent from
* the earliest Midgard's SFBD through the latest Bifrost traces we have),
* there is a hierarchy_mask field, controlling which levels (tile sizes) are
* enabled. Ideally, the hierarchical tiling dream -- mapping big polygons to
* big tiles and small polygons to small tiles -- would be realized here as
* well. As long as there are polygons at all needing tiling, we always have to
* have big tiles available, in case there are big polygons. But we don't
* necessarily need small tiles available. Ideally, when there are small
* polygons, small tiles are enabled (to avoid waste from putting small
* triangles in the big tiles); when there are not, small tiles are disabled to
* avoid enabling more levels than necessary, which potentially costs in memory
* bandwidth / power / tiler performance.
*
* Of course, the driver has to figure this out statically. When tile
* hiearchies are actually established, this occurs by the tiler in
* fixed-function hardware, after the vertex shaders have run and there is
* sufficient information to figure out the size of triangles. The driver has
* no such luxury, again barring insane hacks like additionally running the
* vertex shaders in software or in hardware via transform feedback. Thus, for
* the driver, we need a heuristic approach.
*
* There are lots of heuristics to guess triangle size statically you could
* imagine, but one approach shines as particularly simple-stupid: assume all
* on-screen triangles are equal size and spread equidistantly throughout the
* screen. Let's be clear, this is NOT A VALID ASSUMPTION. But if we roll with
* it, then we see:
*
* Triangle Area = (Screen Area / # of triangles)
* = (Width * Height) / (# of triangles)
*
* Or if you prefer, we can also make a third CRAZY assumption that we only draw
* right triangles with edges parallel/perpendicular to the sides of the screen
* with no overdraw, forming a triangle grid across the screen:
*
* |--w--|
* _____ |
* | /| /| |
* |/_|/_| h
* | /| /| |
* |/_|/_| |
*
* Then you can use some middle school geometry and algebra to work out the
* triangle dimensions. I started working on this, but realised I didn't need
* to to make my point, but couldn't bare to erase that ASCII art. Anyway.
*
* POINT IS, by considering the ratio of screen area and triangle count, we can
* estimate the triangle size. For a small size, use small bins; for a large
* size, use large bins. Intuitively, this metric makes sense: when there are
* few triangles on a large screen, you're probably compositing a UI and
* therefore the triangles are large; when there are a lot of triangles on a
* small screen, you're probably rendering a 3D mesh and therefore the
* triangles are tiny. (Or better said -- there will be tiny triangles, even if
* there are also large triangles. There have to be unless you expect crazy
* overdraw. Generally, it's better to allow more small bin sizes than
* necessary than not allow enough.)
*
* From this heuristic (or whatever), we determine the minimum allowable tile
* size, and we use that to decide the hierarchy masking, selecting from the
* minimum "ideal" tile size to the maximum tile size (2048x2048 in practice).
*
* Once we have that mask and the framebuffer dimensions, we can compute the
* size of the statically-sized polygon list structures, allocate them, and go!
*
* -----
*
* On T720, T820, and T830, there is no support for hierarchical tiling.
* Instead, the hardware allows the driver to select the tile size dynamically
* on a per-framebuffer basis, including allowing rectangular/non-square tiles.
* Rules for tile size selection are as follows:
*
* - Dimensions must be powers-of-two.
* - The smallest tile is 16x16.
* - The tile width/height is at most the framebuffer w/h (clamp up to 16 pix)
* - There must be no more than 64 tiles in either dimension.
*
* Within these constraints, the driver is free to pick a tile size according
* to some heuristic, similar to units with an advanced tiling unit.
*
* To pick a size without any heuristics, we may satisfy the constraints by
* defaulting to 16x16 (a power-of-two). This fits the minimum. For the size
* constraint, consider:
*
* # of tiles < 64
* ceil (fb / tile) < 64
* (fb / tile) <= (64 - 1)
* tile <= fb / (64 - 1) <= next_power_of_two(fb / (64 - 1))
*
* Hence we clamp up to align_pot(fb / (64 - 1)).
* Extending to use a selection heuristic left for future work.
*
* Once the tile size (w, h) is chosen, we compute the hierarchy "mask":
*
* hierarchy_mask = (log2(h / 16) << 6) | log2(w / 16)
*
* Of course with no hierarchical tiling, this is not a mask; it's just a field
* specifying the tile size. But I digress.
*
* We also compute the polgon list sizes (with framebuffer size W, H) as:
*
* full_size = 0x200 + 0x200 * ceil(W / w) * ceil(H / h)
* offset = 8 * ceil(W / w) * ceil(H / h)
*
* It further appears necessary to round down offset to the nearest 0x200.
* Possibly we would also round down full_size to the nearest 0x200 but
* full_size/0x200 = (1 + ceil(W / w) * ceil(H / h)) is an integer so there's
* nothing to do.
*/
/* Hierarchical tiling spans from 16x16 to 4096x4096 tiles */
#define MIN_TILE_SIZE 16
#define MAX_TILE_SIZE 4096
/* Constants as shifts for easier power-of-two iteration */
#define MIN_TILE_SHIFT util_logbase2(MIN_TILE_SIZE)
#define MAX_TILE_SHIFT util_logbase2(MAX_TILE_SIZE)
/* The hierarchy has a 64-byte prologue */
#define PROLOGUE_SIZE 0x40
/* For each tile (across all hierarchy levels), there is 8 bytes of header */
#define HEADER_BYTES_PER_TILE 0x8
/* Likewise, each tile per level has 512 bytes of body */
#define FULL_BYTES_PER_TILE 0x200
static unsigned
pan_hierarchy_size(unsigned width, unsigned height, unsigned mask,
unsigned bytes_per_tile)
{
unsigned size = PROLOGUE_SIZE;
/* Iterate hierarchy levels */
u_foreach_bit(level, mask) {
assert(level <= (MAX_TILE_SHIFT - MIN_TILE_SHIFT) &&
"invalid hierarchy mask");
/* Levels are power-of-two sizes */
unsigned tile_size = MIN_TILE_SIZE << level;
size += DIV_ROUND_UP(width, tile_size) * DIV_ROUND_UP(height, tile_size) *
bytes_per_tile;
}
/* This size will be used as an offset, so ensure it's aligned */
return ALIGN_POT(size, 0x200);
}
/* Implement the formula:
*
* 0x200 + bytes_per_tile * ceil(W / w) * ceil(H / h)
*
* rounding down the answer to the nearest 0x200. This is used to compute both
* header and body sizes for GPUs without hierarchical tiling. Essentially,
* computing a single hierarchy level, since there isn't any hierarchy!
*/
static unsigned
pan_flat_size(unsigned width, unsigned height, unsigned dim,
unsigned bytes_per_tile)
{
/* First, extract the tile dimensions */
unsigned tw = (1 << (dim & 0b111)) * 8;
unsigned th = (1 << ((dim & (0b111 << 6)) >> 6)) * 8;
/* Calculate the raw size */
unsigned raw =
DIV_ROUND_UP(width, tw) * DIV_ROUND_UP(height, th) * bytes_per_tile;
/* Round down and add offset */
return 0x200 + ((raw / 0x200) * 0x200);
}
/* Given a hierarchy mask and a framebuffer size, compute the header size */
unsigned
pan_tiler_header_size(unsigned width, unsigned height, unsigned mask,
bool hierarchy)
{
if (hierarchy)
return pan_hierarchy_size(width, height, mask, HEADER_BYTES_PER_TILE);
else
return pan_flat_size(width, height, mask, HEADER_BYTES_PER_TILE);
}
/* The combined header/body is sized similarly (but it is significantly
* larger), except that it can be empty when the tiler disabled, rather than
* getting clamped to a minimum size.
*/
unsigned
pan_tiler_full_size(unsigned width, unsigned height, unsigned mask,
bool hierarchy)
{
if (hierarchy)
return pan_hierarchy_size(width, height, mask, FULL_BYTES_PER_TILE);
else
return pan_flat_size(width, height, mask, FULL_BYTES_PER_TILE);
}
/* On GPUs without hierarchical tiling, we choose a tile size directly and
* stuff it into the field otherwise known as hierarchy mask (not a mask). */
static unsigned
pan_choose_tile_size(unsigned width, unsigned height, unsigned vertex_count)
{
/* Figure out the ideal tile size. Eventually a heuristic should be
* used for this */
unsigned best_w = 16;
unsigned best_h = 16;
/* Clamp so there are less than 64 tiles in each direction */
best_w = MAX2(best_w, util_next_power_of_two(width / 63));
best_h = MAX2(best_h, util_next_power_of_two(height / 63));
/* We have our ideal tile size, so encode */
unsigned exp_w = util_logbase2(best_w / 16);
unsigned exp_h = util_logbase2(best_h / 16);
return exp_w | (exp_h << 6);
}
unsigned
pan_choose_hierarchy_mask(unsigned width, unsigned height,
unsigned vertex_count, bool hierarchy)
{
/* If there is no geometry, we don't bother enabling anything */
if (!vertex_count)
return 0x00;
if (!hierarchy)
return pan_choose_tile_size(width, height, vertex_count);
/* Heuristic: choose the largest minimum bin size such that there are an
* average of k vertices per bin at the lowest level. This is modeled as:
*
* k = vertex_count / ((fb width / bin width) * (fb height / bin height))
*
* Bins are square, so solving for bin size = bin width = bin height:
*
* bin size = sqrt(((k) (fb width) (fb height) / vertex count))
*
* k = 4 represents each bin as a QUAD. If the screen is completely tiled
* into nonoverlapping uniform power-of-two squares, then this heuristic sets
* the bin size to the quad size, which seems like an ok choice.
*/
unsigned k = 4;
unsigned log2_min_bin_size =
util_logbase2_ceil((k * width * height) / vertex_count) / 2;
/* Do not use bins larger than the framebuffer. They will be empty. */
unsigned log2_max_bin_size = util_logbase2_ceil(MAX2(width, height));
/* For small framebuffers, use one big tile */
log2_min_bin_size = MIN2(log2_min_bin_size, log2_max_bin_size);
/* Clamp to valid bin sizes */
log2_min_bin_size = CLAMP(log2_min_bin_size, MIN_TILE_SHIFT, MAX_TILE_SHIFT);
log2_max_bin_size = CLAMP(log2_max_bin_size, MIN_TILE_SHIFT, MAX_TILE_SHIFT);
/* Bin indices are numbered from 0 started with MIN_TILE_SIZE */
unsigned min_bin_index = log2_min_bin_size - MIN_TILE_SHIFT;
unsigned max_bin_index = log2_max_bin_size - MIN_TILE_SHIFT;
/* Enable up to 8 bins starting from the heuristic selected minimum. 8
* is the implementation specific maximum in supported Midgard devices.
*/
unsigned mask =
(BITFIELD_MASK(8) << min_bin_index) & BITFIELD_MASK(max_bin_index + 1);
assert(mask != 0 && "too few levels");
assert(util_bitcount(mask) <= 8 && "too many levels");
return mask;
}
+102
View File
@@ -0,0 +1,102 @@
/*
* Copyright © 2026 Amazon.com, Inc. or its affiliates.
*
* SPDX-License-Identifier: MIT
*/
#include "pan_trace.h"
#include "util/os_misc.h"
#define PAN_TRACE_ENV_VAR "PAN_CPU_TRACE"
#define CATEGORY(str, flag) { str, ARRAY_SIZE(str) - 1, (uint64_t) (flag) }
/* To be kept in sync with the pan_trace_category enum in pan_trace.h. */
/* clang-format off */
static struct {
const char *str;
size_t len;
uint64_t flag;
} categories_table[] = {
/* Library categories. */
CATEGORY("lib.afbc", PAN_TRACE_LIB_AFBC),
CATEGORY("lib.desc", PAN_TRACE_LIB_DESC),
CATEGORY("lib.kmod", PAN_TRACE_LIB_KMOD),
CATEGORY("lib", PAN_TRACE_LIB_AFBC |
PAN_TRACE_LIB_DESC |
PAN_TRACE_LIB_KMOD),
/* Gallium categories. */
CATEGORY("gl.blit", PAN_TRACE_GL_BLIT),
CATEGORY("gl.bo", PAN_TRACE_GL_BO),
CATEGORY("gl.cmdstream", PAN_TRACE_GL_CMDSTREAM),
CATEGORY("gl.context", PAN_TRACE_GL_CONTEXT),
CATEGORY("gl.csf", PAN_TRACE_GL_CSF),
CATEGORY("gl.disk_cache", PAN_TRACE_GL_DISK_CACHE),
CATEGORY("gl.fb_preload", PAN_TRACE_GL_FB_PRELOAD),
CATEGORY("gl.jm", PAN_TRACE_GL_JM),
CATEGORY("gl.job", PAN_TRACE_GL_JOB),
CATEGORY("gl.mempool", PAN_TRACE_GL_MEMPOOL),
CATEGORY("gl.resource", PAN_TRACE_GL_RESOURCE),
CATEGORY("gl.shader", PAN_TRACE_GL_SHADER),
CATEGORY("gl", PAN_TRACE_GL_BLIT |
PAN_TRACE_GL_BO |
PAN_TRACE_GL_CMDSTREAM |
PAN_TRACE_GL_CONTEXT |
PAN_TRACE_GL_CSF |
PAN_TRACE_GL_DISK_CACHE |
PAN_TRACE_GL_FB_PRELOAD |
PAN_TRACE_GL_JM |
PAN_TRACE_GL_JOB |
PAN_TRACE_GL_MEMPOOL |
PAN_TRACE_GL_RESOURCE |
PAN_TRACE_GL_SHADER),
/* Vulkan categories. */
CATEGORY("vk.csf", PAN_TRACE_VK_CSF),
CATEGORY("vk", PAN_TRACE_VK_CSF),
};
/* clang-format on */
uint64_t pan_trace_categories = 0;
static bool
is_separator(char c)
{
return c == ',' || c == ';' || c == ' ';
}
void
pan_trace_init(void)
{
const char *list = os_get_option(PAN_TRACE_ENV_VAR);
const char *str = NULL;
uint64_t categories = 0;
char prev_char = ',';
if (!list)
return;
/* Parse list and flag enabled categories. */
for (int i = 0; prev_char; prev_char = list[i++]) {
if (!is_separator(list[i]) && list[i]) {
if (is_separator(prev_char))
str = &list[i];
} else if (!is_separator(prev_char)) {
for (int j = 0; j < ARRAY_SIZE(categories_table); j++) {
size_t len = &list[i] - str;
if (categories_table[j].len == len &&
!strncasecmp(categories_table[j].str, str, len)) {
categories |= categories_table[j].flag;
break;
}
}
}
}
pan_trace_categories = categories;
}
+93
View File
@@ -0,0 +1,93 @@
/*
* Copyright © 2026 Amazon.com, Inc. or its affiliates.
*
* SPDX-License-Identifier: MIT
*/
#ifndef __PAN_TRACE_H
#define __PAN_TRACE_H
#include "util/perf/cpu_trace.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Panfrost wrappers for Mesa CPU scope traces.
*
* CPU tracing (not to be confused with GPU command stream tracing with
* PAN_MESA_DEBUG=trace) is often enabled in release builds. Since each trace
* pushed down the underlying tracing backend (Perfetto, Gpuvis, sysprof, etc)
* has a slight cost, Panfrost extends the Mesa CPU scope traces by
* associating each trace to a category that must be enabled at run-time. This
* allows to keep adding useful trace points into Panfrost without worrying
* about the implied inherent latency.
*
* There are 3 categories ("lib" for the shared utilities, "gl" for Gallium,
* "vk" for PanVK) divided into subcategories so that subsystems can easily be
* traced. A list of categories can be passed to Panfrost through the
* PAN_CPU_TRACE environment variable like that:
*
* $ PAN_CPU_TRACE=gl,lib program
*
* Passing a category enables traces for all its subcategories. Subcategories
* can be passed instead for finer grained traces like that:
*
* $ PAN_CPU_TRACE=gl.blit,gl.bo,gl.job program
*/
/* Panfrost trace categories.
*
* To be kept in sync with the categories_table array in pan_trace.c.
*/
/* clang-format off */
enum pan_trace_category {
/* Library categories. */
PAN_TRACE_LIB_AFBC = BITFIELD_BIT(1), /* "lib.afbc" */
PAN_TRACE_LIB_DESC = BITFIELD_BIT(2), /* "lib.desc" */
PAN_TRACE_LIB_KMOD = BITFIELD_BIT(3), /* "lib.kmod" */
/* Gallium categories. */
PAN_TRACE_GL_BLIT = BITFIELD_BIT(4), /* "gl.blit" */
PAN_TRACE_GL_BO = BITFIELD_BIT(5), /* "gl.bo" */
PAN_TRACE_GL_CMDSTREAM = BITFIELD_BIT(6), /* "gl.cmdstream" */
PAN_TRACE_GL_CONTEXT = BITFIELD_BIT(7), /* "gl.context" */
PAN_TRACE_GL_CSF = BITFIELD_BIT(8), /* "gl.csf" */
PAN_TRACE_GL_DISK_CACHE = BITFIELD_BIT(9), /* "gl.disk_cache" */
PAN_TRACE_GL_FB_PRELOAD = BITFIELD_BIT(10), /* "gl.fb_preload" */
PAN_TRACE_GL_JM = BITFIELD_BIT(11), /* "gl.jm" */
PAN_TRACE_GL_JOB = BITFIELD_BIT(12), /* "gl.job" */
PAN_TRACE_GL_MEMPOOL = BITFIELD_BIT(13), /* "gl.mempool" */
PAN_TRACE_GL_RESOURCE = BITFIELD_BIT(14), /* "gl.resource" */
PAN_TRACE_GL_SHADER = BITFIELD_BIT(15), /* "gl.shader" */
/* Vulkan categories. */
PAN_TRACE_VK_CSF = BITFIELD_BIT(16), /* "vk.csf" */
};
/* clang-format on */
extern uint64_t pan_trace_categories;
/* Add a Mesa CPU scope trace for a given Panfrost category using printf like
* formatting.
*/
#define PAN_TRACE_SCOPE(category, format, ...) \
MESA_TRACE_SCOPE_IF(category & pan_trace_categories, format, ##__VA_ARGS__)
/* Add a Mesa CPU scope trace for a given Panfrost category using current
* function name.
*/
#define PAN_TRACE_FUNC(category) \
MESA_TRACE_FUNC_IF(category & pan_trace_categories)
/* Parse the PAN_CPU_TRACE environment variable and initialize CPU tracing.
* The PAN_CPU_TRACE environment variable stores a list of categories (see
* enum pan_trace_category) separated by a comma, a semicolon or a space.
*/
void pan_trace_init(void);
#ifdef __cplusplus
} /* extern C */
#endif
#endif /* __PAN_TRACE_H */
+53
View File
@@ -0,0 +1,53 @@
/*
* Copyright (C) 2019 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#include "pan_util.h"
#include <stdio.h>
/* Translate a PIPE swizzle quad to a 12-bit Mali swizzle code. PIPE
* swizzles line up with Mali swizzles for the XYZW01, but PIPE swizzles have
* an additional "NONE" field that we have to mask out to zero. Additionally,
* PIPE swizzles are sparse but Mali swizzles are packed */
unsigned
pan_translate_swizzle_4(const unsigned char swizzle[4])
{
unsigned out = 0;
for (unsigned i = 0; i < 4; ++i) {
assert(swizzle[i] <= PIPE_SWIZZLE_1);
out |= (swizzle[i] << (3 * i));
}
return out;
}
void
pan_invert_swizzle(const unsigned char *in, unsigned char *out)
{
/* First, default to all zeroes, both to prevent uninitialized junk
and to provide a known baseline so we can tell when components
have been modified
*/
for (unsigned c = 0; c < 4; ++c)
out[c] = PIPE_SWIZZLE_0;
/* Now "do" what the swizzle says */
for (unsigned c = 0; c < 4; ++c) {
unsigned char i = in[c];
/* Who cares? */
assert(PIPE_SWIZZLE_X == 0);
if (i > PIPE_SWIZZLE_W)
continue;
/* Invert (only if we haven't already applied) */
unsigned idx = i - PIPE_SWIZZLE_X;
if (out[idx] == PIPE_SWIZZLE_0)
out[idx] = PIPE_SWIZZLE_X + c;
}
}
+60
View File
@@ -0,0 +1,60 @@
/*
* Copyright 2019 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#ifndef PAN_UTIL_H
#define PAN_UTIL_H
#include <stdbool.h>
#include <stdint.h>
#include "util/format/u_format.h"
#define PAN_DBG_PERF 0x0001
#define PAN_DBG_TRACE 0x0002
/* 0x4 unused */
#define PAN_DBG_DIRTY 0x0008
#define PAN_DBG_SYNC 0x0010
/* 0x20 unused */
#define PAN_DBG_NOFP16 0x0040
#define PAN_DBG_NO_CRC 0x0080
#define PAN_DBG_GL3 0x0100
#define PAN_DBG_NO_AFBC 0x0200
/* 0x400 unused */
#define PAN_DBG_STRICT_IMPORT 0x0800
#define PAN_DBG_LINEAR 0x1000
#define PAN_DBG_NO_CACHE 0x2000
#define PAN_DBG_DUMP 0x4000
#ifndef NDEBUG
#define PAN_DBG_OVERFLOW 0x8000
#endif
#define PAN_DBG_YUV 0x20000
#define PAN_DBG_FORCE_PACK 0x40000
#define PAN_DBG_CS 0x80000
struct pan_blendable_format;
unsigned pan_translate_swizzle_4(const unsigned char swizzle[4]);
void pan_invert_swizzle(const unsigned char *in, unsigned char *out);
void pan_pack_color(const struct pan_blendable_format *blendable_formats,
uint32_t *packed, const union pipe_color_union *color,
enum pipe_format format, bool dithered);
/* Get the last blend shader, for an erratum workaround on v5 */
static inline uint64_t
pan_last_nonnull(uint64_t *ptrs, unsigned count)
{
for (signed i = ((signed)count - 1); i >= 0; --i) {
if (ptrs[i])
return ptrs[i];
}
return 0;
}
#endif /* PAN_UTIL_H */
+67
View File
@@ -0,0 +1,67 @@
/*
* Copyright 2025 Collabora Ltd.
* SPDX-License-Identifier: MIT
*/
#include "pan_encoder.h"
struct test {
const char *label;
uint32_t offset_id;
uint32_t divisor;
};
#define TEST_DEF(label, offset_id, divisor) {label, offset_id, divisor}
static const struct test attribute_tests[] = {
TEST_DEF("Sanity check even", 2, 2),
TEST_DEF("Sanity check odd", 9, 3),
TEST_DEF("Sanity check random #1", 42, 3),
TEST_DEF("Sanity check random #2", 999, 3),
TEST_DEF("Sanity check max #1", UINT32_MAX, UINT32_MAX),
TEST_DEF("Sanity check max #2", UINT32_MAX, INT32_MAX),
TEST_DEF("Regression test case #1", 404896682, 444453),
TEST_DEF("Regression test case #2", 449209605, 980807),
TEST_DEF("Regression test case #3", 412962763, 103240691),
TEST_DEF("Regression test case #4", 1269169964, 507465),
TEST_DEF("Regression test case #5", 1771745081, 6867229),
};
static uint32_t
compute_effective_id(uint64_t offset_id, uint64_t r, uint64_t e, uint64_t d)
{
return (offset_id + e) * (d + (1u << 31)) / ((uint64_t)1 << (32 + r));
}
#define ASSERT_EQ(x, y) \
do { \
if (x == y) { \
nr_pass++; \
} else { \
nr_fail++; \
fprintf(stderr, "%s: Assertion failed %s (%x) != %s (%x)\n", T.label, \
#x, x, #y, y); \
} \
} while (0)
int
main(int argc, const char **argv)
{
unsigned nr_pass = 0, nr_fail = 0;
for (unsigned i = 0; i < ARRAY_SIZE(attribute_tests); ++i) {
struct test T = attribute_tests[i];
uint32_t e;
uint32_t r;
uint32_t d = pan_compute_npot_divisor(T.divisor, &r, &e);
uint32_t expected_effective_id = T.offset_id / T.divisor;
uint32_t computed_effective_id =
compute_effective_id(T.offset_id, r, e, d);
ASSERT_EQ(expected_effective_id, computed_effective_id);
}
printf("Passed %u/%u\n", nr_pass, nr_pass + nr_fail);
return nr_fail ? 1 : 0;
}
+308
View File
@@ -0,0 +1,308 @@
/*
* Copyright (C) 2021 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#include "pan_blend.h"
/* A test consists of a given blend mode and its translated form */
struct test {
const char *label;
struct pan_blend_equation eq;
unsigned constant_mask;
bool reads_dest;
bool opaque;
bool fixed_function;
bool alpha_zero_nop;
bool alpha_one_store;
uint32_t hardware;
};
/* clang-format off */
#define RGBA(key, value) \
.rgb_ ## key = value, \
.alpha_ ## key = value
static const struct test blend_tests[] = {
{
"Replace",
{
.blend_enable = false,
.color_mask = 0xF,
},
.constant_mask = 0x0,
.reads_dest = false,
.opaque = true,
.fixed_function = true,
.alpha_zero_nop = false,
.alpha_one_store = false,
.hardware = 0xF0122122
},
{
"Alpha",
{
.blend_enable = true,
.color_mask = 0xF,
RGBA(func, PIPE_BLEND_ADD),
RGBA(src_factor, PIPE_BLENDFACTOR_SRC_ALPHA),
RGBA(dst_factor, PIPE_BLENDFACTOR_INV_SRC_ALPHA),
},
.constant_mask = 0x0,
.reads_dest = true,
.opaque = false,
.fixed_function = true,
.alpha_zero_nop = true,
.alpha_one_store = true,
.hardware = 0xF0503503
},
{
"Additive",
{
.blend_enable = true,
.color_mask = 0xF,
RGBA(func, PIPE_BLEND_ADD),
RGBA(src_factor, PIPE_BLENDFACTOR_ONE),
RGBA(dst_factor, PIPE_BLENDFACTOR_ONE),
},
.constant_mask = 0x0,
.reads_dest = true,
.opaque = false,
.fixed_function = true,
.alpha_zero_nop = false,
.alpha_one_store = false,
.hardware = 0xF0932932 /* equivalently 0xF0923923 */
},
{
"Additive-Alpha",
{
.blend_enable = true,
.color_mask = 0xF,
RGBA(func, PIPE_BLEND_ADD),
RGBA(src_factor, PIPE_BLENDFACTOR_SRC_ALPHA),
RGBA(dst_factor, PIPE_BLENDFACTOR_ONE),
},
.constant_mask = 0x0,
.reads_dest = true,
.opaque = false,
.fixed_function = true,
.alpha_zero_nop = true,
.alpha_one_store = false,
.hardware = 0xF0523523
},
{
"Subtractive",
{
.blend_enable = true,
.color_mask = 0xF,
RGBA(func, PIPE_BLEND_SUBTRACT),
RGBA(src_factor, PIPE_BLENDFACTOR_ONE),
RGBA(dst_factor, PIPE_BLENDFACTOR_ONE),
},
.constant_mask = 0x0,
.reads_dest = true,
.opaque = false,
.fixed_function = true,
.alpha_zero_nop = false,
.alpha_one_store = false,
.hardware = 0xF09B29B2 /* equivalently 0xF09A39A3 */
},
{
"Subtractive-Alpha",
{
.blend_enable = true,
.color_mask = 0xF,
RGBA(func, PIPE_BLEND_SUBTRACT),
RGBA(src_factor, PIPE_BLENDFACTOR_SRC_ALPHA),
RGBA(dst_factor, PIPE_BLENDFACTOR_ONE),
},
.constant_mask = 0x0,
.reads_dest = true,
.opaque = false,
.fixed_function = true,
.alpha_zero_nop = false,
.alpha_one_store = false,
.hardware = 0xF052B52b /* equivalently 0xF05A35A3 */
},
{
"Modulate",
{
.blend_enable = true,
.color_mask = 0xF,
RGBA(func, PIPE_BLEND_ADD),
RGBA(src_factor, PIPE_BLENDFACTOR_ZERO),
RGBA(dst_factor, PIPE_BLENDFACTOR_SRC_COLOR),
},
.constant_mask = 0x0,
.reads_dest = true,
.opaque = false,
.fixed_function = true,
.alpha_zero_nop = false,
.alpha_one_store = false,
.hardware = 0xF0231231 /* equivalently 0xF0321321 */
},
{
"Replace masked",
{
.blend_enable = false,
.color_mask = 0x3,
},
.constant_mask = 0x0,
.reads_dest = true,
.opaque = false,
.fixed_function = true,
.alpha_zero_nop = false,
.alpha_one_store = false,
.hardware = 0x30122122
},
{
"Modulate masked",
{
.blend_enable = true,
.color_mask = 0xA,
RGBA(func, PIPE_BLEND_ADD),
RGBA(src_factor, PIPE_BLENDFACTOR_ZERO),
RGBA(dst_factor, PIPE_BLENDFACTOR_SRC_COLOR),
},
.constant_mask = 0x0,
.reads_dest = true,
.opaque = false,
.fixed_function = true,
.alpha_zero_nop = false,
.alpha_one_store = false,
.hardware = 0xA0231231 /* equivalently 0xA0321321 */
},
{
"src*dst + dst*src",
{
.blend_enable = true,
.color_mask = 0xF,
RGBA(func, PIPE_BLEND_ADD),
RGBA(src_factor, PIPE_BLENDFACTOR_DST_COLOR),
RGBA(dst_factor, PIPE_BLENDFACTOR_SRC_COLOR),
},
.constant_mask = 0x0,
.reads_dest = true,
.opaque = false,
.fixed_function = true,
.alpha_zero_nop = false,
.alpha_one_store = false,
.hardware = 0xF0431431 /* 0 + dest * (2*src) */
},
{
"Mixed src*dst + dst*src masked I",
{
.blend_enable = true,
.color_mask = 0xC,
.rgb_func = PIPE_BLEND_ADD,
.rgb_src_factor = PIPE_BLENDFACTOR_ONE,
.rgb_dst_factor= PIPE_BLENDFACTOR_ZERO,
.alpha_func = PIPE_BLEND_ADD,
.alpha_src_factor = PIPE_BLENDFACTOR_DST_COLOR,
.alpha_dst_factor= PIPE_BLENDFACTOR_SRC_COLOR,
},
.constant_mask = 0x0,
.reads_dest = true,
.opaque = false,
.fixed_function = true,
.alpha_zero_nop = false,
.alpha_one_store = false,
.hardware = 0xC0431132 /* 0 + dest * (2*src); equivalent 0xC0431122 */
},
{
"Mixed src*dst + dst*src masked II",
{
.blend_enable = true,
.color_mask = 0xC,
.rgb_func = PIPE_BLEND_ADD,
.rgb_src_factor = PIPE_BLENDFACTOR_ONE,
.rgb_dst_factor = PIPE_BLENDFACTOR_ZERO,
.alpha_func = PIPE_BLEND_ADD,
.alpha_src_factor = PIPE_BLENDFACTOR_DST_ALPHA,
.alpha_dst_factor= PIPE_BLENDFACTOR_SRC_COLOR,
},
.constant_mask = 0x0,
.reads_dest = true,
.opaque = false,
.fixed_function = true,
.alpha_zero_nop = false,
.alpha_one_store = false,
.hardware = 0xC0431132 /* 0 + dest * (2*src); equivalent 0xC0431122 */
},
{
"Mixed src*dst + dst*src masked III",
{
.blend_enable = true,
.color_mask = 0xC,
.rgb_func = PIPE_BLEND_ADD,
.rgb_src_factor = PIPE_BLENDFACTOR_ONE,
.rgb_dst_factor = PIPE_BLENDFACTOR_ZERO,
.alpha_func = PIPE_BLEND_ADD,
.alpha_src_factor = PIPE_BLENDFACTOR_DST_ALPHA,
.alpha_dst_factor= PIPE_BLENDFACTOR_SRC_ALPHA,
},
.constant_mask = 0x0,
.reads_dest = true,
.opaque = false,
.fixed_function = true,
.alpha_zero_nop = false,
.alpha_one_store = false,
.hardware = 0xC0431132 /* 0 + dest * (2*src); equivalent 0xC0431122 */
}
};
/* clang-format on */
#define ASSERT_EQ(x, y) \
do { \
if (x == y) { \
nr_pass++; \
} else { \
nr_fail++; \
fprintf(stderr, "%s: Assertion failed %s (%x) != %s (%x)\n", T.label, \
#x, x, #y, y); \
} \
} while (0)
int
main(int argc, const char **argv)
{
unsigned nr_pass = 0, nr_fail = 0;
/* The architecture we test for is arbitrary and used only for checking
* whether we can use fixed function. If we really wanted to be paranoid
* we could add a loop checking all architectures, but in practice there's
* not much difference and we're only checking for internal consistency
* anyway
*/
unsigned arch = 10;
for (unsigned i = 0; i < ARRAY_SIZE(blend_tests); ++i) {
struct test T = blend_tests[i];
ASSERT_EQ(T.constant_mask, pan_blend_constant_mask(T.eq));
ASSERT_EQ(T.reads_dest, pan_blend_reads_dest(T.eq));
ASSERT_EQ(T.opaque, pan_blend_is_opaque(T.eq));
ASSERT_EQ(T.fixed_function, pan_blend_can_fixed_function(arch, T.eq, true));
ASSERT_EQ(T.alpha_zero_nop, pan_blend_alpha_zero_nop(T.eq));
ASSERT_EQ(T.alpha_one_store, pan_blend_alpha_one_store(T.eq));
if (pan_blend_can_fixed_function(arch, T.eq, true)) {
ASSERT_EQ(T.hardware, pan_pack_blend(T.eq));
}
}
printf("Passed %u/%u\n", nr_pass, nr_pass + nr_fail);
return nr_fail ? 1 : 0;
}
+169
View File
@@ -0,0 +1,169 @@
/*
* Copyright (C) 2021 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#include "pan_image.h"
#include "pan_util.h"
/* A test consists of a render target format, clear colour, dither state, and
* translated form. Dither state matters when the tilebuffer format is more
* precise than the final format. */
struct test {
enum pipe_format format;
bool dithered;
union pipe_color_union colour;
uint32_t packed[4];
};
#define RRRR(r) \
{ \
r, r, r, r \
}
#define RGRG(r, g) \
{ \
r, g, r, g \
}
#define F(r, g, b, a) \
{ \
.f = { r, g, b, a } \
}
#define UI(r, g, b, a) \
{ \
.ui = { r, g, b, a } \
}
#define D (true)
#define _ (false)
/* clang-format off */
static const struct test clear_tests[] = {
/* Basic tests */
{ PIPE_FORMAT_R8G8B8A8_UNORM, D, F(0.0, 0.0, 0.0, 0.0), RRRR(0x00000000) },
{ PIPE_FORMAT_R8G8B8A8_UNORM, _, F(0.0, 0.0, 0.0, 0.0), RRRR(0x00000000) },
{ PIPE_FORMAT_R8G8B8A8_UNORM, D, F(1.0, 0.0, 0.0, 1.0), RRRR(0xFF0000FF) },
{ PIPE_FORMAT_R8G8B8A8_UNORM, _, F(1.0, 0.0, 0.0, 1.0), RRRR(0xFF0000FF) },
{ PIPE_FORMAT_B8G8R8A8_UNORM, D, F(1.0, 0.0, 0.0, 1.0), RRRR(0xFF0000FF) },
{ PIPE_FORMAT_B8G8R8A8_UNORM, _, F(1.0, 0.0, 0.0, 1.0), RRRR(0xFF0000FF) },
{ PIPE_FORMAT_R8G8B8A8_UNORM, D, F(0.664, 0.0, 0.0, 0.0), RRRR(0x000000A9) },
{ PIPE_FORMAT_R8G8B8A8_UNORM, _, F(0.664, 0.0, 0.0, 0.0), RRRR(0x000000A9) },
{ PIPE_FORMAT_R4G4B4A4_UNORM, D, F(0.664, 0.0, 0.0, 0.0), RRRR(0x0000009F) },
{ PIPE_FORMAT_R4G4B4A4_UNORM, _, F(0.664, 0.0, 0.0, 0.0), RRRR(0x000000A0) },
/* Test rounding to nearest even. The values are cherrypicked to multiply
* out to a fractional part of 0.5. The first test should round down and
* second test should round up. */
{ PIPE_FORMAT_R4G4B4A4_UNORM, D, F(0.41875, 0.0, 0.0, 1.0), RRRR(0xF0000064) },
{ PIPE_FORMAT_R4G4B4A4_UNORM, D, F(0.40625, 0.0, 0.0, 1.0), RRRR(0xF0000062) },
/* Testing rounding direction when dithering is disabled. The packed value
* is what gets rounded. This behaves as round-to-even with the cutoff point
* at 2^-m + 2^-n for an m:n representation. */
{ PIPE_FORMAT_R4G4B4A4_UNORM, _, F(0.03125, 0.0, 0.0, 1.0), RRRR(0xF0000000) },
{ PIPE_FORMAT_R4G4B4A4_UNORM, _, F(0.03125, 0.0, 0.0, 1.0), RRRR(0xF0000000) },
{ PIPE_FORMAT_R4G4B4A4_UNORM, _, F(0.03333, 0.0, 0.0, 1.0), RRRR(0xF0000000) },
{ PIPE_FORMAT_R4G4B4A4_UNORM, _, F(0.03334, 0.0, 0.0, 1.0), RRRR(0xF0000010) },
{ PIPE_FORMAT_R4G4B4A4_UNORM, _, F(0.09375, 0.0, 0.0, 1.0), RRRR(0xF0000010) },
/* Check all the special formats with different edge cases */
{ PIPE_FORMAT_R4G4B4A4_UNORM, D, F(0.127, 2.4, -1.0, 0.5), RRRR(0x7800F01E) },
{ PIPE_FORMAT_R5G5B5A1_UNORM, D, F(0.127, 2.4, -1.0, 0.5), RRRR(0x400F807E) },
{ PIPE_FORMAT_R5G6B5_UNORM, D, F(0.127, 2.4, -1.0, 0.5), RRRR(0x000FC07E) },
{ PIPE_FORMAT_R10G10B10A2_UNORM, D, F(0.127, 2.4, -1.0, 0.5), RRRR(0x800FFC82) },
{ PIPE_FORMAT_R8G8B8A8_SRGB, D, F(0.127, 2.4, -1.0, 0.5), RRRR(0x8000FF64) },
{ PIPE_FORMAT_R4G4B4A4_UNORM, D, F(0.718, 0.18, 1.0, 2.0), RRRR(0xF0F02BAC) },
{ PIPE_FORMAT_R5G6B5_UNORM, D, F(0.718, 0.18, 1.0, 2.0), RRRR(0x3E02D6C8) },
{ PIPE_FORMAT_R5G5B5A1_UNORM, D, F(0.718, 0.18, 1.0, 2.0), RRRR(0xBE02CEC8) },
{ PIPE_FORMAT_R10G10B10A2_UNORM, D, F(0.718, 0.18, 1.0, 2.0), RRRR(0xFFF2E2DF) },
{ PIPE_FORMAT_R8G8B8A8_SRGB, D, F(0.718, 0.18, 1.0, 2.0), RRRR(0xFFFF76DC) },
/* Check that values are padded when dithering is disabled */
{ PIPE_FORMAT_R4G4B4A4_UNORM, _, F(0.718, 0.18, 1.0, 2.0), RRRR(0xF0F030B0) },
{ PIPE_FORMAT_R5G6B5_UNORM, _, F(0.718, 0.18, 1.0, 2.0), RRRR(0x3E02C2C0) },
{ PIPE_FORMAT_R5G5B5A1_UNORM, _, F(0.718, 0.18, 1.0, 2.0), RRRR(0xBE0302C0) },
{ PIPE_FORMAT_R10G10B10A2_UNORM, _, F(0.718, 0.18, 1.0, 2.0), RRRR(0xFFF2E2DF) },
{ PIPE_FORMAT_R8G8B8A8_SRGB, _, F(0.718, 0.18, 1.0, 2.0), RRRR(0xFFFF76DC) },
/* Check that blendable tilebuffer values are invariant under swizzling */
{ PIPE_FORMAT_B4G4R4A4_UNORM, D, F(0.127, 2.4, -1.0, 0.5), RRRR(0x7800F01E) },
{ PIPE_FORMAT_B5G5R5A1_UNORM, D, F(0.127, 2.4, -1.0, 0.5), RRRR(0x400F807E) },
{ PIPE_FORMAT_B5G6R5_UNORM, D, F(0.127, 2.4, -1.0, 0.5), RRRR(0x000FC07E) },
{ PIPE_FORMAT_B10G10R10A2_UNORM, D, F(0.127, 2.4, -1.0, 0.5), RRRR(0x800FFC82) },
{ PIPE_FORMAT_B8G8R8A8_SRGB, D, F(0.127, 2.4, -1.0, 0.5), RRRR(0x8000FF64) },
{ PIPE_FORMAT_B4G4R4A4_UNORM, D, F(0.718, 0.18, 1.0, 2.0), RRRR(0xF0F02BAC) },
{ PIPE_FORMAT_B5G6R5_UNORM, D, F(0.718, 0.18, 1.0, 2.0), RRRR(0x3E02D6C8) },
{ PIPE_FORMAT_B5G5R5A1_UNORM, D, F(0.718, 0.18, 1.0, 2.0), RRRR(0xBE02CEC8) },
{ PIPE_FORMAT_B10G10R10A2_UNORM, D, F(0.718, 0.18, 1.0, 2.0), RRRR(0xFFF2E2DF) },
{ PIPE_FORMAT_B8G8R8A8_SRGB, D, F(0.718, 0.18, 1.0, 2.0), RRRR(0xFFFF76DC) },
{ PIPE_FORMAT_B4G4R4A4_UNORM, _, F(0.718, 0.18, 1.0, 2.0), RRRR(0xF0F030B0) },
{ PIPE_FORMAT_B5G6R5_UNORM, _, F(0.718, 0.18, 1.0, 2.0), RRRR(0x3E02C2C0) },
{ PIPE_FORMAT_B5G5R5A1_UNORM, _, F(0.718, 0.18, 1.0, 2.0), RRRR(0xBE0302C0) },
{ PIPE_FORMAT_B10G10R10A2_UNORM, _, F(0.718, 0.18, 1.0, 2.0), RRRR(0xFFF2E2DF) },
{ PIPE_FORMAT_B8G8R8A8_SRGB, _, F(0.718, 0.18, 1.0, 2.0), RRRR(0xFFFF76DC) },
/* Check raw formats, which are not invariant under swizzling. Raw formats
* cannot be dithered. */
{ PIPE_FORMAT_R8G8B8A8_UINT, D, UI(0xCA, 0xFE, 0xBA, 0xBE), RRRR(0xBEBAFECA) },
{ PIPE_FORMAT_R8G8B8A8_UINT, _, UI(0xCA, 0xFE, 0xBA, 0xBE), RRRR(0xBEBAFECA) },
{ PIPE_FORMAT_B8G8R8A8_UINT, D, UI(0xCA, 0xFE, 0xBA, 0xBE), RRRR(0xBECAFEBA) },
{ PIPE_FORMAT_B8G8R8A8_UINT, _, UI(0xCA, 0xFE, 0xBA, 0xBE), RRRR(0xBECAFEBA) },
/* Check that larger raw formats are replicated correctly */
{ PIPE_FORMAT_R16G16B16A16_UINT, D, UI(0xCAFE, 0xBABE, 0xABAD, 0x1DEA),
RGRG(0xBABECAFE, 0x1DEAABAD) },
{ PIPE_FORMAT_R16G16B16A16_UINT, _, UI(0xCAFE, 0xBABE, 0xABAD, 0x1DEA),
RGRG(0xBABECAFE, 0x1DEAABAD) },
{ PIPE_FORMAT_R32G32B32A32_UINT, D,
UI(0xCAFEBABE, 0xABAD1DEA, 0xDEADBEEF, 0xABCDEF01),
{ 0xCAFEBABE, 0xABAD1DEA, 0xDEADBEEF, 0xABCDEF01 } },
{ PIPE_FORMAT_R32G32B32A32_UINT, _,
UI(0xCAFEBABE, 0xABAD1DEA, 0xDEADBEEF, 0xABCDEF01),
{ 0xCAFEBABE, 0xABAD1DEA, 0xDEADBEEF, 0xABCDEF01 } },
};
/* clang-format on */
#define ASSERT_EQ(x, y) \
do { \
if ((x[0] == y[0]) && (x[1] == y[1]) && (x[2] == y[2]) && \
(x[3] == y[3])) { \
nr_pass++; \
} else { \
nr_fail++; \
fprintf( \
stderr, \
"%s%s: Assertion failed %s (%08X %08X %08X %08X) != %s (%08X %08X %08X %08X)\n", \
util_format_short_name(T.format), T.dithered ? " dithered" : "", \
#x, x[0], x[1], x[2], x[3], #y, y[0], y[1], y[2], y[3]); \
} \
} while (0)
int
main(int argc, const char **argv)
{
unsigned nr_pass = 0, nr_fail = 0;
for (unsigned i = 0; i < ARRAY_SIZE(clear_tests); ++i) {
struct test T = clear_tests[i];
uint32_t packed[4];
pan_pack_color(pan_blendable_formats_v7, &packed[0], &T.colour, T.format,
T.dithered);
ASSERT_EQ(T.packed, packed);
}
printf("Passed %u/%u\n", nr_pass, nr_pass + nr_fail);
return nr_fail ? 1 : 0;
}
+167
View File
@@ -0,0 +1,167 @@
/*
* Copyright (C) 2022 Collabora, Ltd.
* SPDX-License-Identifier: MIT
*/
#include "compiler/pan_compiler.h"
#include "pan_earlyzs.h"
#include "genxml/gen_macros.h"
#include <gtest/gtest.h>
/*
* Test the early-ZS helpers used on Bifrost and Valhall. Early-ZS state depends
* on both shader state and draw-time API state. As such, there are two helpers
* -- analzye and get -- that separate the link-time analysis of a fragment
* shader from the draw-time classification. The internal data structure is not
* under test, only the external API. So we test only the composition.
*/
#define ZS_WRITEMASK BITFIELD_BIT(0)
#define ALPHA2COV BITFIELD_BIT(1)
#define ZS_ALWAYS_PASSES BITFIELD_BIT(2)
#define DISCARD BITFIELD_BIT(3)
#define WRITES_Z BITFIELD_BIT(4)
#define WRITES_S BITFIELD_BIT(5)
#define WRITES_COV BITFIELD_BIT(6)
#define SIDEFX BITFIELD_BIT(7)
#define API_EARLY BITFIELD_BIT(8)
#define SHADER_READS_ZS BITFIELD_BIT(9)
#define ARCH_HAS_READONLY_ZS_OPT BITFIELD_BIT(10)
#define ARCH_HAS_STATE_TRACK_OPT BITFIELD_BIT(11)
static void
test(enum mali_pixel_kill expected_update, enum mali_pixel_kill expected_kill,
bool expected_shader_readonly_zs, uint32_t flags)
{
enum pan_earlyzs_zs_tilebuf_read zs_read = PAN_EARLYZS_ZS_TILEBUF_NOT_READ;
struct pan_shader_info info = {};
info.fs.can_discard = !!(flags & DISCARD);
info.fs.writes_depth = !!(flags & WRITES_Z);
info.fs.writes_stencil = !!(flags & WRITES_S);
info.fs.writes_coverage = !!(flags & WRITES_COV);
info.fs.early_fragment_tests = !!(flags & API_EARLY);
info.writes_global = !!(flags & SIDEFX);
unsigned arch = 9;
if (flags & ARCH_HAS_STATE_TRACK_OPT)
arch = 11;
else if (flags & ARCH_HAS_READONLY_ZS_OPT)
arch = 10;
if (flags & SHADER_READS_ZS) {
if (flags & (WRITES_Z | WRITES_S))
zs_read = PAN_EARLYZS_ZS_TILEBUF_READ_NO_OPT;
else
zs_read = PAN_EARLYZS_ZS_TILEBUF_READ_OPT;
}
struct pan_earlyzs_state result = pan_earlyzs_get(
pan_earlyzs_analyze(&info, arch), !!(flags & ZS_WRITEMASK),
!!(flags & ALPHA2COV), !!(flags & ZS_ALWAYS_PASSES), zs_read);
ASSERT_EQ(result.update, expected_update);
ASSERT_EQ(result.kill, expected_kill);
ASSERT_EQ(result.shader_readonly_zs, expected_shader_readonly_zs);
}
#define CASE(expected_update, expected_kill, flags) \
test(MALI_PIXEL_KILL_##expected_update, MALI_PIXEL_KILL_##expected_kill, false, \
flags)
#define CASE_RO_ZS(expected_update, expected_kill, expected_ro_zs, flags) \
test(MALI_PIXEL_KILL_##expected_update, MALI_PIXEL_KILL_##expected_kill, \
expected_ro_zs, flags)
TEST(EarlyZS, APIForceEarly)
{
CASE(FORCE_EARLY, FORCE_EARLY, API_EARLY);
CASE(FORCE_EARLY, FORCE_EARLY, API_EARLY | WRITES_Z | WRITES_S);
CASE(FORCE_EARLY, FORCE_EARLY, API_EARLY | ALPHA2COV | DISCARD);
}
TEST(EarlyZS, ShaderCalculatesZS)
{
CASE(FORCE_LATE, FORCE_LATE, WRITES_Z);
CASE(FORCE_LATE, FORCE_LATE, WRITES_S);
CASE(FORCE_LATE, FORCE_LATE, WRITES_Z | WRITES_S);
CASE(FORCE_LATE, FORCE_LATE, WRITES_Z | WRITES_S | SIDEFX);
CASE(FORCE_LATE, FORCE_LATE, WRITES_Z | WRITES_S | ZS_ALWAYS_PASSES);
CASE(FORCE_LATE, FORCE_LATE, WRITES_Z | ZS_ALWAYS_PASSES | ALPHA2COV);
}
TEST(EarlyZS, ModifiesCoverageWritesZSNoSideFX)
{
CASE(FORCE_LATE, FORCE_EARLY, ZS_WRITEMASK | WRITES_COV);
CASE(FORCE_LATE, FORCE_EARLY, ZS_WRITEMASK | DISCARD);
CASE(FORCE_LATE, FORCE_EARLY, ZS_WRITEMASK | ALPHA2COV);
CASE(FORCE_LATE, FORCE_EARLY,
ZS_WRITEMASK | WRITES_COV | DISCARD | ALPHA2COV);
}
TEST(EarlyZS, ModifiesCoverageWritesZSNoSideFXAlt)
{
CASE(FORCE_LATE, WEAK_EARLY, ZS_ALWAYS_PASSES | ZS_WRITEMASK | WRITES_COV);
CASE(FORCE_LATE, WEAK_EARLY, ZS_ALWAYS_PASSES | ZS_WRITEMASK | DISCARD);
CASE(FORCE_LATE, WEAK_EARLY, ZS_ALWAYS_PASSES | ZS_WRITEMASK | ALPHA2COV);
CASE(FORCE_LATE, WEAK_EARLY,
ZS_ALWAYS_PASSES | ZS_WRITEMASK | WRITES_COV | DISCARD | ALPHA2COV);
}
TEST(EarlyZS, ModifiesCoverageWritesZSSideFX)
{
CASE(FORCE_LATE, FORCE_LATE, ZS_WRITEMASK | SIDEFX | WRITES_COV);
CASE(FORCE_LATE, FORCE_LATE, ZS_WRITEMASK | SIDEFX | DISCARD);
CASE(FORCE_LATE, FORCE_LATE, ZS_WRITEMASK | SIDEFX | ALPHA2COV);
CASE(FORCE_LATE, FORCE_LATE,
ZS_WRITEMASK | SIDEFX | WRITES_COV | DISCARD | ALPHA2COV);
}
TEST(EarlyZS, SideFXNoShaderZS)
{
CASE(FORCE_EARLY, FORCE_LATE, SIDEFX);
CASE(FORCE_EARLY, FORCE_LATE, SIDEFX | DISCARD);
CASE(FORCE_EARLY, FORCE_LATE, SIDEFX | WRITES_COV);
CASE(FORCE_LATE, FORCE_LATE, SIDEFX | ALPHA2COV);
}
TEST(EarlyZS, SideFXNoShaderZSAlt)
{
CASE(WEAK_EARLY, FORCE_LATE, ZS_ALWAYS_PASSES | SIDEFX);
CASE(WEAK_EARLY, FORCE_LATE, ZS_ALWAYS_PASSES | SIDEFX | DISCARD);
CASE(WEAK_EARLY, FORCE_LATE, ZS_ALWAYS_PASSES | SIDEFX | WRITES_COV);
CASE(FORCE_LATE, FORCE_LATE, ZS_ALWAYS_PASSES | SIDEFX | ALPHA2COV);
}
TEST(EarlyZS, NoSideFXNoShaderZS)
{
CASE(FORCE_EARLY, FORCE_EARLY, 0);
CASE(FORCE_LATE, FORCE_EARLY, ALPHA2COV | DISCARD | WRITES_COV);
CASE(FORCE_EARLY, FORCE_EARLY, ZS_WRITEMASK);
}
TEST(EarlyZS, ShaderReadZS)
{
CASE_RO_ZS(FORCE_LATE, FORCE_LATE, false, SIDEFX | SHADER_READS_ZS);
CASE_RO_ZS(FORCE_EARLY, FORCE_LATE, true,
SIDEFX | SHADER_READS_ZS | ARCH_HAS_READONLY_ZS_OPT);
CASE_RO_ZS(FORCE_LATE, FORCE_LATE, false,
SIDEFX | SHADER_READS_ZS | WRITES_Z | ARCH_HAS_READONLY_ZS_OPT);
CASE_RO_ZS(FORCE_EARLY, FORCE_EARLY, true,
SHADER_READS_ZS | ARCH_HAS_READONLY_ZS_OPT);
CASE_RO_ZS(FORCE_LATE, FORCE_LATE, false,
SHADER_READS_ZS | WRITES_Z | ARCH_HAS_READONLY_ZS_OPT);
CASE_RO_ZS(FORCE_LATE, WEAK_EARLY, false,
SHADER_READS_ZS | ZS_ALWAYS_PASSES);
CASE_RO_ZS(FORCE_LATE, FORCE_EARLY, false, SHADER_READS_ZS);
}
TEST(EarlyZS, NoSideFXNoShaderZSAlt)
{
CASE(WEAK_EARLY, WEAK_EARLY, ZS_ALWAYS_PASSES);
CASE(FORCE_EARLY, WEAK_EARLY, ZS_ALWAYS_PASSES | ARCH_HAS_STATE_TRACK_OPT);
CASE(FORCE_LATE, WEAK_EARLY,
ZS_ALWAYS_PASSES | ALPHA2COV | DISCARD | WRITES_COV);
CASE(WEAK_EARLY, WEAK_EARLY, ZS_ALWAYS_PASSES | ZS_WRITEMASK);
}
File diff suppressed because it is too large. Load diff
+57
View File
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2017-2019 Lyude Paul
* Copyright (C) 2017-2019 Alyssa Rosenzweig
* SPDX-License-Identifier: MIT
*/
#ifndef __PAN_DECODE_PUBLIC_H__
#define __PAN_DECODE_PUBLIC_H__
#include <inttypes.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
/* Public entrypoints for the tracing infrastructure. This API should be kept
* more or less stable. Don't feel bad if you have to change it; just feel
* slightly guilty about creating more work for me later. -Alyssa <3
*
* I'm joking. Mostly. panwrap (out-of-tree) includes this, so update that if
* you need to change something here. panwrap is open-source but cannot be
* included in-tree.
*/
// TODO: update panwrap
struct pandecode_context;
struct pandecode_context *pandecode_create_context(bool to_stderr);
void pandecode_next_frame(struct pandecode_context *ctx);
void pandecode_destroy_context(struct pandecode_context *ctx);
void pandecode_inject_mmap(struct pandecode_context *ctx, uint64_t gpu_va,
void *cpu, unsigned sz, const char *name);
void pandecode_inject_free(struct pandecode_context *ctx, uint64_t gpu_va,
unsigned sz);
void pandecode_jc(struct pandecode_context *ctx, uint64_t jc_gpu_va,
uint64_t gpu_id);
void pandecode_interpret_cs(struct pandecode_context *ctx,
uint64_t queue_gpu_va, uint32_t size,
uint64_t gpu_id, uint32_t *regs);
void pandecode_cs_binary(struct pandecode_context *ctx, uint64_t binary_gpu_va,
uint32_t size, uint64_t gpu_id);
void pandecode_cs_trace(struct pandecode_context *ctx, uint64_t trace_gpu_va,
uint32_t size, uint64_t gpu_id);
void pandecode_abort_on_fault(struct pandecode_context *ctx, uint64_t jc_gpu_va,
uint64_t gpu_id);
#endif /* __MMAP_TRACE_H__ */