kgsl_ioctl_gpu_command

// ioctl命令:IOCTL_KGSL_GPU_COMMAND
// ioctl参数:kgsl_gpu_command
// ioctl函数:kgsl_ioctl_gpu_command
KGSL_IOCTL_FUNC(IOCTL_KGSL_GPU_COMMAND, kgsl_ioctl_gpu_command),

1. kgsl_ioctl_gpu_command

long kgsl_ioctl_gpu_command(struct kgsl_device_private *dev_priv,
		unsigned int cmd, void *data)
{
    // IOCTL_KGSL_GPU_COMMAND命令参数[见1.1节]
	struct kgsl_gpu_command *param = data;
	struct kgsl_device *device = dev_priv->device;
	struct kgsl_context *context;
    // kgsl_drawobj[见第1.2节]数组:用于存放kgsl_drawobj_sync和kgsl_drawobj_cmd中的kgsl_drawobj
	struct kgsl_drawobj *drawobj[2];
	unsigned int type;
	long result;
	unsigned int i = 0;

    // 根据参数计算对象类型[见第2节]
	type = _process_command_input(device, param->flags, param->numcmds,
			param->numobjs, param->numsyncs);
	if (!type)
		return -EINVAL;

    // 根据context id查找对应的kgsl_context
	context = kgsl_context_get_owner(dev_priv, param->context_id);
	if (context == NULL)
		return -EINVAL;

    // 1.如果指定KGSL_DRAWOBJ_SYNC标志位, 则命令类型为SYNCOBJ_TYPE
    // 2.如果传入的kgsl_gpu_command的numsyncs大于0, 则命令类型包含SYNCOBJ_TYPE
	if (type & SYNCOBJ_TYPE) {
		struct kgsl_drawobj_sync *syncobj =
				kgsl_drawobj_sync_create(device, context);

		if (IS_ERR(syncobj)) {
			result = PTR_ERR(syncobj);
			goto done;
		}

		drawobj[i++] = DRAWOBJ(syncobj);

		result = kgsl_drawobj_sync_add_synclist(device, syncobj,
				u64_to_user_ptr(param->synclist),
				param->syncsize, param->numsyncs);
		if (result)
			goto done;
	}

    // 1.如果指定KGSL_DRAWOBJ_MARKER标志位, 则命令类型为MARKEROBJ_TYPE
    // 2.如果传入的kgsl_gpu_command的numcmds大于0, 则命令类型包含CMDOBJ_TYPE
    // 3.如果传入的kgsl_gpu_command的numcmds等于0, 则命令类型为MARKEROBJ_TYPE
	if (type & (CMDOBJ_TYPE | MARKEROBJ_TYPE)) {
        // 调用kgsl_drawobj_cmd_create[见第3节]创建kgsl_drawobj_cmd
		struct kgsl_drawobj_cmd *cmdobj =
				kgsl_drawobj_cmd_create(device,
					context, param->flags, type);

		if (IS_ERR(cmdobj)) {
			result = PTR_ERR(cmdobj);
			goto done;
		}

        // drawobj数组元素加1
		drawobj[i++] = DRAWOBJ(cmdobj);

        // 将cmdlist插入kgsl_drawobj_cmd的cmdlist链表[第4节]
		result = kgsl_drawobj_cmd_add_cmdlist(device, cmdobj,
			u64_to_user_ptr(param->cmdlist),
			param->cmdsize, param->numcmds);
		if (result)
			goto done;

        // 将objlist插入kgsl_drawobj_cmd的memlist链表[第5节]
		result = kgsl_drawobj_cmd_add_memlist(device, cmdobj,
			u64_to_user_ptr(param->objlist),
			param->objsize, param->numobjs);
		if (result)
			goto done;

		/* If no profiling buffer was specified, clear the flag */
		if (cmdobj->profiling_buf_entry == NULL)
			DRAWOBJ(cmdobj)->flags &=
				~(unsigned long)KGSL_DRAWOBJ_PROFILING;
	}

    // 调用adreno_functable中定义的adreno_queue_cmds[第6节]将kgsl_drawobj提交给ringbuffer
	result = device->ftbl->queue_cmds(dev_priv, context, drawobj,
				i, &param->timestamp);

done:
	/*
	 * -EPROTO is a "success" error - it just tells the user that the
	 * context had previously faulted
	 */
	if (result && result != -EPROTO)
		while (i--)
			kgsl_drawobj_destroy(drawobj[i]);

	kgsl_context_put(context);
	return result;
}

1.1 kgsl_gpu_command

/**
 * struct kgsl_command_object - Argument for IOCTL_KGSL_GPU_COMMAND
 */
struct kgsl_gpu_command {
    // 标志位
	__u64 flags;

    // 指向kgsl_command_object链表的指针(for submission)
	__u64 __user cmdlist;
    // kgsl_command_object的大小
	unsigned int cmdsize;
    // cmdlist中kgsl_command_object的数量
	unsigned int numcmds;

    // 指向kgsl_command_object链表的指针(for tracking)
	__u64 __user objlist;
    // kgsl_command_object的大小
	unsigned int objsize;
    // objlist中kgsl_command_object的数量
	unsigned int numobjs;

    // 指向kgsl_command_syncpoint链表的指针
    __u64 __user synclist;
    // kgsl_command_syncpoint的大小
	unsigned int syncsize;
    // synclist中kgsl_command_syncpoint的数量
	unsigned int numsyncs;

    // kgsl_context的id
	unsigned int context_id;
    // Timestamp for the submitted commands
	unsigned int timestamp;
};

1.2 kgsl_drawobj

/**
 * struct kgsl_drawobj - KGSL drawobj descriptor
 */
// kgsl_drawobj的容器为kgsl_drawobj_cmd[见1.3节]
struct kgsl_drawobj {
    // KGSL GPU device that the command was created for
	struct kgsl_device *device;
    // KGSL context that created the command
	struct kgsl_context *context;
    // Object type
	uint32_t type;
    // Timestamp assigned to the command
	uint32_t timestamp;
    // 标志位
	unsigned long flags;
    // 引用计数
	struct kref refcount;
	/** @destroy: Callbak function to take down the object */
	void (*destroy)(struct kgsl_drawobj *drawobj);
	/** @destroy_object: Callback function to free the object memory */
	void (*destroy_object)(struct kgsl_drawobj *drawobj);
};

1.3 kgsl_drawobj_cmd

/**
 * struct kgsl_drawobj_cmd - KGSL command obj, This covers marker
 * cmds also since markers are special form of cmds that do not
 * need their cmds to be executed.
 */
struct kgsl_drawobj_cmd {
    // 第一个成员
	struct kgsl_drawobj base;
    // Internal flags
	unsigned long priv;

    // The ringbuffer timestamp corresponding to this command obj
	unsigned int global_ts;

    unsigned long fault_policy;
	unsigned long fault_recovery;

    // List of IBs to issue
	struct list_head cmdlist;
    // List of all memory used in this command batch
	struct list_head memlist;
    // For markers, the timestamp of the last "real" command that was queued
	unsigned int marker_timestamp;

    // profiling
	struct kgsl_mem_entry *profiling_buf_entry;
	uint64_t profiling_buffer_gpuaddr;
	unsigned int profile_index;

    // Variable to hold ticks at the time of command obj submit
	uint64_t submit_ticks;
};

2. _process_command_input

// kgsl_command_object最多100000个
#define KGSL_MAX_NUMIBS 100000
// kgsl_command_syncpoint最多32个
#define KGSL_MAX_SYNCPOINTS 32

#define CMDOBJ_TYPE     BIT(0)
#define MARKEROBJ_TYPE  BIT(1)
#define SYNCOBJ_TYPE    BIT(2)
#define TIMELINEOBJ_TYPE    BIT(4)

/* Returns 0 on failure.  Returns command type(s) on success */
static unsigned int _process_command_input(struct kgsl_device *device,
		unsigned int flags, unsigned int numcmds,
		unsigned int numobjs, unsigned int numsyncs)
{
    // kgsl_gpu_command的成员numcmds, numobjs和numsyncs不能超过最大限制
	if (numcmds > KGSL_MAX_NUMIBS ||
			numobjs > KGSL_MAX_NUMIBS ||
			numsyncs > KGSL_MAX_SYNCPOINTS)
		return 0;

	/*
	 * The SYNC bit is supposed to identify a dummy sync object
	 * so warn the user if they specified any IBs with it.
	 * A MARKER command can either have IBs or not but if the
	 * command has 0 IBs it is automatically assumed to be a marker.
	 */

	/* If they specify the flag, go with what they say */
    // 首先根据标志位判断对象类型
	if (flags & KGSL_DRAWOBJ_MARKER)
		return MARKEROBJ_TYPE;
	else if (flags & KGSL_DRAWOBJ_SYNC)
		return SYNCOBJ_TYPE;

	/* If not, deduce what they meant */
    // 然后根据numsyncs和numcmds确定对象类型
	if (numsyncs && numcmds)
		return SYNCOBJ_TYPE | CMDOBJ_TYPE;
	else if (numsyncs)
		return SYNCOBJ_TYPE;
	else if (numcmds)
		return CMDOBJ_TYPE;
	else if (numcmds == 0)
		return MARKEROBJ_TYPE;

	return 0;
}

3. kgsl_drawobj_cmd_create

/**
 * kgsl_drawobj_cmd_create() - Create a new command obj structure
 * Allocate a new kgsl_drawobj_cmd structure
 */
struct kgsl_drawobj_cmd *kgsl_drawobj_cmd_create(struct kgsl_device *device,
		struct kgsl_context *context, unsigned int flags,
		unsigned int type)
{
    // 分配kgsl_drawobj_cmd
	struct kgsl_drawobj_cmd *cmdobj = kzalloc(sizeof(*cmdobj), GFP_KERNEL);
	int ret;

	if (!cmdobj)
		return ERR_PTR(-ENOMEM);

    // 初始化kgsl_drawobj[见3.1节]
	ret = drawobj_init(device, context, &cmdobj->base,
		(type & (CMDOBJ_TYPE | MARKEROBJ_TYPE)));
	if (ret) {
		kfree(cmdobj);
		return ERR_PTR(ret);
	}

    // 设置kgsl_drawobj的destroy回调
	cmdobj->base.destroy = cmdobj_destroy;
    // 设置kgsl_drawobj的destroy_object回调
	cmdobj->base.destroy_object = cmdobj_destroy_object;

	/* sanitize our flags for drawobjs */
    // 设置kgsl_drawobj的标志位
	cmdobj->base.flags = flags & (KGSL_DRAWOBJ_CTX_SWITCH
		| KGSL_DRAWOBJ_MARKER
		| KGSL_DRAWOBJ_END_OF_FRAME
		| KGSL_DRAWOBJ_PWR_CONSTRAINT
		| KGSL_DRAWOBJ_MEMLIST
		| KGSL_DRAWOBJ_PROFILING
		| KGSL_DRAWOBJ_PROFILING_KTIME);

    // 初始化kgsl_drawobj_cmd的cmdlist
	INIT_LIST_HEAD(&cmdobj->cmdlist);
    // 初始化kgsl_drawobj_cmd的memlist
	INIT_LIST_HEAD(&cmdobj->memlist);

	return cmdobj;
}

3.1 drawobj_init

static int drawobj_init(struct kgsl_device *device,
	struct kgsl_context *context, struct kgsl_drawobj *drawobj,
	int type)
{
	/*
	 * Increase the reference count on the context so it doesn't disappear
	 * during the lifetime of this object
	 */
    // 增加kgsl_context引用计数
	if (!_kgsl_context_get(context))
		return -ENOENT;

    // 初始化kgsl_drawobj的引用计数
	kref_init(&drawobj->refcount);
	drawobj->device = device;
    // 设置kgsl_drawobj所属的kgsl_context
	drawobj->context = context;
    // 设置kgsl_drawobj类型
	drawobj->type = type;

	return 0;
}

4. kgsl_drawobj_cmd_add_cmdlist

/* This can only accept MARKEROBJ_TYPE and CMDOBJ_TYPE */
// ptr:kgsl_gpu_command的cmdlist
// size:kgsl_gpu_command的cmdsize
// count:kgsl_gpu_command的numcmds
int kgsl_drawobj_cmd_add_cmdlist(struct kgsl_device *device,
		struct kgsl_drawobj_cmd *cmdobj, void __user *ptr,
		unsigned int size, unsigned int count)
{
    // kgsl_command_object[见4.1节]
	struct kgsl_command_object obj;
	struct kgsl_drawobj *baseobj = DRAWOBJ(cmdobj);
	int i, ret;

	/* Ignore everything if this is a MARKER */
	if (baseobj->type & MARKEROBJ_TYPE)
		return 0;

    // 参数校验
	ret = _verify_input_list(count, ptr, size);
	if (ret <= 0)
		return ret;

    // 遍历每一个cmd
	for (i = 0; i < count; i++) {
		memset(&obj, 0, sizeof(obj));

        // 将用户空间传入的ptr指向的大小为size的对象拷贝到kgsl_command_object
		ret = kgsl_copy_from_user(&obj, ptr, sizeof(obj), size);
		if (ret)
			return ret;

		/* Sanity check the flags */
		if (!(obj.flags & CMDLIST_FLAGS)) {
			dev_err(device->dev,
				     "invalid cmdobj ctxt %d flags %d id %d offset %llu addr %llx size %llu\n",
				     baseobj->context->id, obj.flags, obj.id,
				     obj.offset, obj.gpuaddr, obj.size);
			return -EINVAL;
		}

        // 将kgsl_command_object添加到kgsl_drawobj_cmd的链表cmdlist[见4.2节]
		ret = kgsl_drawobj_add_memobject(&cmdobj->cmdlist, &obj);
		if (ret)
			return ret;

        // ptr指针向后移动以遍历下一个cmd
		ptr += sizeof(obj);
	}

	return 0;
}

4.1 kgsl_command_object

/**
 * struct kgsl_command_object - GPU command object
 */
struct kgsl_command_object {
    // GPU address offset of the object
	__u64 offset;
    // GPU address of the object
	__u64 gpuaddr;
    // Size of the object
	__u64 size;
    // Current flags for the object
	unsigned int flags;
    // GPU command object ID
	unsigned int id;
};

4.2 kgsl_drawobj_add_memobject

static int kgsl_drawobj_add_memobject(struct list_head *head,
		struct kgsl_command_object *obj)
{
	struct kgsl_memobj_node *mem;

    // 创建kgsl_memobj_node[见4.2.1节]
	mem = kmem_cache_alloc(memobjs_cache, GFP_KERNEL);
	if (mem == NULL)
		return -ENOMEM;

    // 将kgsl_command_object的成员赋值给kgsl_memobj_node
	mem->gpuaddr = obj->gpuaddr;
	mem->size = obj->size;
	mem->id = obj->id;
	mem->offset = obj->offset;
	mem->flags = obj->flags;
    // 初始化priv成员
	mem->priv = 0;

    // 将kgsl_memobj_node插入到kgsl_drawobj_cmd链表cmdlist的表尾
	list_add_tail(&mem->node, head);
	return 0;
}

4.2.1 kgsl_memobj_node

/**
 * struct kgsl_memobj_node - Memory object descriptor
 * @node: Local list node for the object
 * @id: GPU memory ID for the object
 * offset: Offset within the object
 * @gpuaddr: GPU address for the object
 * @flags: External flags passed by the user
 * @priv: Internal flags set by the driver
 */
struct kgsl_memobj_node {
	struct list_head node;
	unsigned int id;
	uint64_t offset;
	uint64_t gpuaddr;
	uint64_t size;
	unsigned long flags;
	unsigned long priv;
};

5. kgsl_drawobj_cmd_add_memlist

// ptr:kgsl_gpu_command的objlist
// size:kgsl_gpu_command的objsize
// count:kgsl_gpu_command的numobjs
int kgsl_drawobj_cmd_add_memlist(struct kgsl_device *device,
		struct kgsl_drawobj_cmd *cmdobj, void __user *ptr,
		unsigned int size, unsigned int count)
{
	struct kgsl_command_object obj;
	struct kgsl_drawobj *baseobj = DRAWOBJ(cmdobj);
	int i, ret;

	/* Ignore everything if this is a MARKER */
	if (baseobj->type & MARKEROBJ_TYPE)
		return 0;

	ret = _verify_input_list(count, ptr, size);
	if (ret <= 0)
		return ret;

    // 遍历每一个cmd
	for (i = 0; i < count; i++) {
		memset(&obj, 0, sizeof(obj));

        // 将用户空间传入的ptr指向的大小为size的对象拷贝到kgsl_command_object
		ret = kgsl_copy_from_user(&obj, ptr, sizeof(obj), size);
		if (ret)
			return ret;

		if (!(obj.flags & KGSL_OBJLIST_MEMOBJ)) {
			dev_err(device->dev,
				     "invalid memobj ctxt %d flags %d id %d offset %lld addr %lld size %lld\n",
				     DRAWOBJ(cmdobj)->context->id, obj.flags,
				     obj.id, obj.offset, obj.gpuaddr,
				     obj.size);
			return -EINVAL;
		}

		if (obj.flags & KGSL_OBJLIST_PROFILE)
			add_profiling_buffer(device, cmdobj, obj.gpuaddr,
				obj.size, obj.id, obj.offset);
		else {
            // 将kgsl_command_object添加到kgsl_drawobj_cmd的链表memlist
			ret = kgsl_drawobj_add_memobject(&cmdobj->memlist,
				&obj);
			if (ret)
				return ret;
		}

        // ptr指针向后移动以遍历下一个cmd
		ptr += sizeof(obj);
	}

	return 0;
}

6. adreno_queue_cmds

static int adreno_queue_cmds(struct kgsl_device_private *dev_priv,
	struct kgsl_context *context, struct kgsl_drawobj *drawobj[],
	u32 count, u32 *timestamp)
{
	struct kgsl_device *device = dev_priv->device;

	if (test_bit(GMU_DISPATCH, &device->gmu_core.flags))
		return adreno_hwsched_queue_cmds(dev_priv, context, drawobj,
				count, timestamp);

    // Queue a new draw object in the context[见第7节]
	return adreno_dispatcher_queue_cmds(dev_priv, context, drawobj, count,
			timestamp);
}

7. adreno_dispatcher_queue_cmds

/* Number of commands that can be queued in a context before it sleeps */
static unsigned int _context_drawqueue_size = 50;

/**
 * adreno_dispactcher_queue_cmds() - Queue a new draw object in the context
 * @dev_priv: Pointer to the device private struct
 * @context: Pointer to the kgsl draw context
 * @drawobj: Pointer to the array of drawobj's being submitted
 * @count: Number of drawobj's being submitted
 * @timestamp: Pointer to the requested timestamp
 *
 * Queue a command in the context - if there isn't any room in the queue, then
 * block until there is
 */
int adreno_dispatcher_queue_cmds(struct kgsl_device_private *dev_priv,
		struct kgsl_context *context, struct kgsl_drawobj *drawobj[],
		uint32_t count, uint32_t *timestamp)

{
	struct kgsl_device *device = dev_priv->device;
	struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
	struct adreno_context *drawctxt = ADRENO_CONTEXT(context);
	struct adreno_dispatcher_drawqueue *dispatch_q;
	struct adreno_dispatch_job *job;
	int ret;
	unsigned int i, user_ts;

	/*
	 * There is always a possibility that dispatcher may end up pushing
	 * the last popped draw object back to the context drawqueue. Hence,
	 * we can only queue up to _context_drawqueue_size - 1 here to make
	 * sure we never let drawqueue->queued exceed _context_drawqueue_size.
	 */
	if (!count || count > _context_drawqueue_size - 1)
		return -EINVAL;

    // 检查kgsl_context的状态
	ret = _check_context_state(&drawctxt->base);
	if (ret)
		return ret;

    // 检查kgsl_drawobj_cmd的cmdlist链表中的每个kgsl_memobj_node的有效性
	ret = _verify_cmdobj(dev_priv, context, drawobj, count);
	if (ret)
		return ret;

	/* wait for the suspend gate */
	wait_for_completion(&device->halt_gate);

	job = kmem_cache_alloc(jobs_cache, GFP_KERNEL);
	if (!job)
		return -ENOMEM;

	job->drawctxt = drawctxt;

	spin_lock(&drawctxt->lock);

	ret = _check_context_state_to_queue_cmds(drawctxt, count);
	if (ret) {
		spin_unlock(&drawctxt->lock);
		kmem_cache_free(jobs_cache, job);
		return ret;
	}

	user_ts = *timestamp;

	/*
	 * If there is only one drawobj in the array and it is of
	 * type SYNCOBJ_TYPE, skip comparing user_ts as it can be 0
	 */
	if (!(count == 1 && drawobj[0]->type == SYNCOBJ_TYPE) &&
		(drawctxt->base.flags & KGSL_CONTEXT_USER_GENERATED_TS)) {
		/*
		 * User specified timestamps need to be greater than the last
		 * issued timestamp in the context
		 */
		if (timestamp_cmp(drawctxt->timestamp, user_ts) >= 0) {
			spin_unlock(&drawctxt->lock);
			kmem_cache_free(jobs_cache, job);
			return -ERANGE;
		}
	}

    // 遍历传入的每个kgsl_drawobj
	for (i = 0; i < count; i++) {
		switch (drawobj[i]->type) {
		case MARKEROBJ_TYPE:
			ret = drawctxt_queue_markerobj(adreno_dev, drawctxt,
				drawobj[i], timestamp, user_ts);
			if (ret) {
				spin_unlock(&drawctxt->lock);
				kmem_cache_free(jobs_cache, job);
			}

			if (ret == 1)
				goto done;
			else if (ret)
				return ret;
			break;
		case CMDOBJ_TYPE:
            // drawctxt_queue_cmdobj[见7.1节]
			ret = drawctxt_queue_cmdobj(adreno_dev, drawctxt,
				drawobj[i], timestamp, user_ts);
			if (ret) {
				spin_unlock(&drawctxt->lock);
				kmem_cache_free(jobs_cache, job);
				return ret;
			}
			break;
		case SYNCOBJ_TYPE:
			drawctxt_queue_syncobj(drawctxt, drawobj[i], timestamp);
			break;
		case TIMELINEOBJ_TYPE:
			ret = drawctxt_queue_auxobj(adreno_dev,
				drawctxt, drawobj[i], timestamp, user_ts);
			if (ret) {
				spin_unlock(&drawctxt->lock);
				kmem_cache_free(jobs_cache, job);
				return ret;
			}
			break;
		default:
			spin_unlock(&drawctxt->lock);
			kmem_cache_free(jobs_cache, job);
			return -EINVAL;
		}

	}

    // 返回adreno_ringbuffer中的adreno_dispatcher_drawqueue
	dispatch_q = ADRENO_DRAWOBJ_DISPATCH_DRAWQUEUE(drawobj[0]);

	_track_context(adreno_dev, dispatch_q, drawctxt);

	spin_unlock(&drawctxt->lock);

	/* Add the context to the dispatcher pending list */
	if (_kgsl_context_get(&drawctxt->base)) {
		trace_dispatch_queue_context(drawctxt);
		llist_add(&job->node,
			&adreno_dev->dispatcher.jobs[drawctxt->base.priority]);
	} else {
		kmem_cache_free(jobs_cache, job);
		goto done;
	}

	/*
	 * Only issue commands if inflight is less than burst -this prevents us
	 * from sitting around waiting for the mutex on a busy system - the work
	 * loop will schedule it for us. Inflight is mutex protected but the
	 * worse that can happen is that it will go to 0 after we check and if
	 * it goes to 0 it is because the work loop decremented it and the work
	 * queue will try to schedule new commands anyway.
	 */

	if (dispatch_q->inflight < _context_drawobj_burst)
		adreno_dispatcher_issuecmds(adreno_dev);
done:
	if (test_and_clear_bit(ADRENO_CONTEXT_FAULT, &context->priv))
		return -EPROTO;

	return 0;
}

7.1 drawctxt_queue_cmdobj

static int drawctxt_queue_cmdobj(struct adreno_device *adreno_dev,
	struct adreno_context *drawctxt, struct kgsl_drawobj *drawobj,
	uint32_t *timestamp, unsigned int user_ts)
{
	struct kgsl_drawobj_cmd *cmdobj = CMDOBJ(drawobj);
	unsigned int j;
	int ret;

	ret = get_timestamp(drawctxt, drawobj, timestamp, user_ts);
	if (ret)
		return ret;

	/*
	 * If this is a real command then we need to force any markers
	 * queued before it to dispatch to keep time linear - set the
	 * skip bit so the commands get NOPed.
	 */
	j = drawctxt->drawqueue_head;

	while (j != drawctxt->drawqueue_tail) {
		if (drawctxt->drawqueue[j]->type == MARKEROBJ_TYPE) {
			struct kgsl_drawobj_cmd *markerobj =
				CMDOBJ(drawctxt->drawqueue[j]);
				set_bit(CMDOBJ_SKIP, &markerobj->priv);
		}

		j = DRAWQUEUE_NEXT(j, ADRENO_CONTEXT_DRAWQUEUE_SIZE);
	}

	drawctxt->queued_timestamp = *timestamp;
	_set_ft_policy(adreno_dev, drawctxt, cmdobj);
	_cmdobj_set_flags(drawctxt, cmdobj);

	_queue_drawobj(drawctxt, drawobj);

	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值