1 /*
2 * Copyright © 2022 Imagination Technologies Ltd.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 * SOFTWARE.
22 */
23
24 #include <stdbool.h>
25 #include <stdint.h>
26
27 #include "hwdef/rogue_hw_utils.h"
28 #include "pvr_bo.h"
29 #include "pvr_device_info.h"
30 #include "pvr_formats.h"
31 #include "pvr_hw_pass.h"
32 #include "pvr_pds.h"
33 #include "pvr_private.h"
34 #include "pvr_usc_fragment_shader.h"
35 #include "rogue/rogue.h"
36 #include "vk_alloc.h"
37 #include "vk_format.h"
38 #include "vk_log.h"
39
40 /*****************************************************************************
41 PDS pre-baked program generation parameters and variables.
42 *****************************************************************************/
43 /* These would normally be produced by the compiler or other code. We're using
44 * them for now just to speed up things. All of these should eventually be
45 * removed.
46 */
47
48 static const struct {
49 /* Indicates the amount of temporaries for the shader. */
50 uint32_t temp_count;
51 enum rogue_msaa_mode msaa_mode;
52 /* Indicates the presence of PHAS instruction. */
53 bool has_phase_rate_change;
54 } pvr_pds_fragment_program_params = {
55 .temp_count = 0,
56 .msaa_mode = ROGUE_MSAA_MODE_PIXEL,
57 .has_phase_rate_change = false,
58 };
59
pvr_subpass_has_msaa_input_attachment(struct pvr_render_subpass * subpass,const VkRenderPassCreateInfo2 * pCreateInfo)60 static inline bool pvr_subpass_has_msaa_input_attachment(
61 struct pvr_render_subpass *subpass,
62 const VkRenderPassCreateInfo2 *pCreateInfo)
63 {
64 for (uint32_t i = 0; i < subpass->input_count; i++) {
65 const uint32_t attachment = subpass->input_attachments[i];
66
67 if (pCreateInfo->pAttachments[attachment].samples > 1)
68 return true;
69 }
70
71 return false;
72 }
73
74 static inline size_t
pvr_num_subpass_attachments(const VkSubpassDescription2 * desc)75 pvr_num_subpass_attachments(const VkSubpassDescription2 *desc)
76 {
77 return desc->inputAttachmentCount + desc->colorAttachmentCount +
78 (desc->pResolveAttachments ? desc->colorAttachmentCount : 0) +
79 (desc->pDepthStencilAttachment != NULL);
80 }
81
pvr_is_subpass_initops_flush_needed(const struct pvr_render_pass * pass,const struct pvr_renderpass_hwsetup_render * hw_render)82 static bool pvr_is_subpass_initops_flush_needed(
83 const struct pvr_render_pass *pass,
84 const struct pvr_renderpass_hwsetup_render *hw_render)
85 {
86 struct pvr_render_subpass *subpass = &pass->subpasses[0];
87 uint32_t render_loadop_mask = 0;
88 uint32_t color_attachment_mask;
89
90 for (uint32_t i = 0; i < hw_render->color_init_count; i++) {
91 if (hw_render->color_init[i].op != RENDERPASS_SURFACE_INITOP_NOP)
92 render_loadop_mask |= (1 << hw_render->color_init[i].driver_id);
93 }
94
95 /* If there are no load ops then there's nothing to flush. */
96 if (render_loadop_mask == 0)
97 return false;
98
99 /* If the first subpass has any input attachments, they need to be
100 * initialized with the result of the load op. Since the input attachment
101 * may be read from fragments with an opaque pass type, the load ops must be
102 * flushed or else they would be obscured and eliminated by HSR.
103 */
104 if (subpass->input_count != 0)
105 return true;
106
107 color_attachment_mask = 0;
108
109 for (uint32_t i = 0; i < subpass->color_count; i++) {
110 const int32_t color_idx = subpass->color_attachments[i];
111
112 if (color_idx != -1)
113 color_attachment_mask |= (1 << pass->attachments[color_idx].index);
114 }
115
116 /* If the first subpass does not write to all attachments which have a load
117 * op then the load ops need to be flushed to ensure they don't get obscured
118 * and removed by HSR.
119 */
120 return (render_loadop_mask & color_attachment_mask) != render_loadop_mask;
121 }
122
123 static void
pvr_init_subpass_userpass_spawn(struct pvr_renderpass_hwsetup * hw_setup,struct pvr_render_pass * pass,struct pvr_render_subpass * subpasses)124 pvr_init_subpass_userpass_spawn(struct pvr_renderpass_hwsetup *hw_setup,
125 struct pvr_render_pass *pass,
126 struct pvr_render_subpass *subpasses)
127 {
128 uint32_t subpass_idx = 0;
129
130 for (uint32_t i = 0; i < hw_setup->render_count; i++) {
131 struct pvr_renderpass_hwsetup_render *hw_render = &hw_setup->renders[i];
132 uint32_t initial_userpass_spawn =
133 (uint32_t)pvr_is_subpass_initops_flush_needed(pass, hw_render);
134
135 for (uint32_t j = 0; j < hw_render->subpass_count; j++) {
136 subpasses[subpass_idx].userpass_spawn = (j + initial_userpass_spawn);
137 subpass_idx++;
138 }
139 }
140
141 assert(subpass_idx == pass->subpass_count);
142 }
143
pvr_has_output_register_writes(const struct pvr_renderpass_hwsetup_render * hw_render)144 static inline bool pvr_has_output_register_writes(
145 const struct pvr_renderpass_hwsetup_render *hw_render)
146 {
147 for (uint32_t i = 0; i < hw_render->init_setup.render_targets_count; i++) {
148 struct usc_mrt_resource *mrt_resource =
149 &hw_render->init_setup.mrt_resources[i];
150
151 if (mrt_resource->type == USC_MRT_RESOURCE_TYPE_OUTPUT_REGISTER)
152 return true;
153 }
154
155 return false;
156 }
157
pvr_pds_unitex_state_program_create_and_upload(struct pvr_device * device,const VkAllocationCallbacks * allocator,uint32_t texture_kicks,uint32_t uniform_kicks,struct pvr_pds_upload * const pds_upload_out)158 VkResult pvr_pds_unitex_state_program_create_and_upload(
159 struct pvr_device *device,
160 const VkAllocationCallbacks *allocator,
161 uint32_t texture_kicks,
162 uint32_t uniform_kicks,
163 struct pvr_pds_upload *const pds_upload_out)
164 {
165 struct pvr_pds_pixel_shader_sa_program program = {
166 .num_texture_dma_kicks = texture_kicks,
167 .num_uniform_dma_kicks = uniform_kicks,
168 };
169 uint32_t staging_buffer_size;
170 uint32_t *staging_buffer;
171 VkResult result;
172
173 pvr_pds_set_sizes_pixel_shader_uniform_texture_code(&program);
174
175 staging_buffer_size = program.code_size * sizeof(*staging_buffer);
176
177 staging_buffer = vk_alloc2(&device->vk.alloc,
178 allocator,
179 staging_buffer_size,
180 8U,
181 VK_SYSTEM_ALLOCATION_SCOPE_COMMAND);
182 if (!staging_buffer)
183 return vk_error(device, VK_ERROR_OUT_OF_HOST_MEMORY);
184
185 pvr_pds_generate_pixel_shader_sa_code_segment(&program, staging_buffer);
186
187 /* FIXME: Figure out the define for alignment of 16. */
188 result = pvr_gpu_upload_pds(device,
189 NULL,
190 0U,
191 0U,
192 staging_buffer,
193 program.code_size,
194 16U,
195 16U,
196 pds_upload_out);
197 if (result != VK_SUCCESS) {
198 vk_free2(&device->vk.alloc, allocator, staging_buffer);
199 return result;
200 }
201
202 vk_free2(&device->vk.alloc, allocator, staging_buffer);
203
204 return VK_SUCCESS;
205 }
206
207 static VkResult
pvr_load_op_create(struct pvr_device * device,const VkAllocationCallbacks * allocator,struct pvr_renderpass_hwsetup_render * hw_render,struct pvr_load_op ** const load_op_out)208 pvr_load_op_create(struct pvr_device *device,
209 const VkAllocationCallbacks *allocator,
210 struct pvr_renderpass_hwsetup_render *hw_render,
211 struct pvr_load_op **const load_op_out)
212 {
213 const struct pvr_device_info *dev_info = &device->pdevice->dev_info;
214 const uint32_t cache_line_size = rogue_get_slc_cache_line_size(dev_info);
215 struct pvr_load_op *load_op;
216 VkResult result;
217
218 load_op = vk_zalloc2(&device->vk.alloc,
219 allocator,
220 sizeof(*load_op),
221 8,
222 VK_SYSTEM_ALLOCATION_SCOPE_DEVICE);
223 if (!load_op)
224 return vk_error(device, VK_ERROR_OUT_OF_HOST_MEMORY);
225
226 for (uint32_t i = 0; i < hw_render->color_init_count; i++) {
227 struct pvr_renderpass_colorinit *color_init = &hw_render->color_init[i];
228
229 if (color_init->op == RENDERPASS_SURFACE_INITOP_CLEAR)
230 load_op->clear_mask |= 1U << i;
231 else if (color_init->op == RENDERPASS_SURFACE_INITOP_LOAD)
232 pvr_finishme("Missing 'load' load op");
233 }
234
235 result = pvr_gpu_upload_usc(device,
236 pvr_usc_fragment_shader,
237 sizeof(pvr_usc_fragment_shader),
238 cache_line_size,
239 &load_op->usc_frag_prog_bo);
240 if (result != VK_SUCCESS)
241 goto err_free_load_op;
242
243 result = pvr_pds_fragment_program_create_and_upload(
244 device,
245 allocator,
246 load_op->usc_frag_prog_bo,
247 pvr_pds_fragment_program_params.temp_count,
248 pvr_pds_fragment_program_params.msaa_mode,
249 pvr_pds_fragment_program_params.has_phase_rate_change,
250 &load_op->pds_frag_prog);
251 if (result != VK_SUCCESS)
252 goto err_free_usc_frag_prog_bo;
253
254 result = pvr_pds_unitex_state_program_create_and_upload(
255 device,
256 allocator,
257 1U,
258 0U,
259 &load_op->pds_tex_state_prog);
260 if (result != VK_SUCCESS)
261 goto err_free_pds_frag_prog;
262
263 load_op->is_hw_object = true;
264 /* FIXME: These should be based on the USC and PDS programs, but are hard
265 * coded for now.
266 */
267 load_op->const_shareds_count = 1;
268 load_op->shareds_dest_offset = 0;
269 load_op->shareds_count = 1;
270 load_op->temps_count = 1;
271
272 *load_op_out = load_op;
273
274 return VK_SUCCESS;
275
276 err_free_pds_frag_prog:
277 pvr_bo_free(device, load_op->pds_frag_prog.pvr_bo);
278
279 err_free_usc_frag_prog_bo:
280 pvr_bo_free(device, load_op->usc_frag_prog_bo);
281
282 err_free_load_op:
283 vk_free2(&device->vk.alloc, allocator, load_op);
284
285 return result;
286 }
287
pvr_load_op_destroy(struct pvr_device * device,const VkAllocationCallbacks * allocator,struct pvr_load_op * load_op)288 static void pvr_load_op_destroy(struct pvr_device *device,
289 const VkAllocationCallbacks *allocator,
290 struct pvr_load_op *load_op)
291 {
292 pvr_bo_free(device, load_op->pds_tex_state_prog.pvr_bo);
293 pvr_bo_free(device, load_op->pds_frag_prog.pvr_bo);
294 pvr_bo_free(device, load_op->usc_frag_prog_bo);
295 vk_free2(&device->vk.alloc, allocator, load_op);
296 }
297
298 #define PVR_SPM_LOAD_IN_BUFFERS_COUNT(dev_info) \
299 ({ \
300 int __ret = 7U; \
301 if (PVR_HAS_FEATURE(dev_info, eight_output_registers)) \
302 __ret = 3U; \
303 __ret; \
304 })
305
pvr_CreateRenderPass2(VkDevice _device,const VkRenderPassCreateInfo2 * pCreateInfo,const VkAllocationCallbacks * pAllocator,VkRenderPass * pRenderPass)306 VkResult pvr_CreateRenderPass2(VkDevice _device,
307 const VkRenderPassCreateInfo2 *pCreateInfo,
308 const VkAllocationCallbacks *pAllocator,
309 VkRenderPass *pRenderPass)
310 {
311 struct pvr_render_pass_attachment *attachments;
312 PVR_FROM_HANDLE(pvr_device, device, _device);
313 struct pvr_render_subpass *subpasses;
314 size_t subpass_attachment_count;
315 uint32_t *subpass_attachments;
316 struct pvr_render_pass *pass;
317 uint32_t *dep_list;
318 bool *flush_on_dep;
319 VkResult result;
320
321 VK_MULTIALLOC(ma);
322 vk_multialloc_add(&ma, &pass, __typeof__(*pass), 1);
323 vk_multialloc_add(&ma,
324 &attachments,
325 __typeof__(*attachments),
326 pCreateInfo->attachmentCount);
327 vk_multialloc_add(&ma,
328 &subpasses,
329 __typeof__(*subpasses),
330 pCreateInfo->subpassCount);
331
332 subpass_attachment_count = 0;
333 for (uint32_t i = 0; i < pCreateInfo->subpassCount; i++) {
334 subpass_attachment_count +=
335 pvr_num_subpass_attachments(&pCreateInfo->pSubpasses[i]);
336 }
337
338 vk_multialloc_add(&ma,
339 &subpass_attachments,
340 __typeof__(*subpass_attachments),
341 subpass_attachment_count);
342 vk_multialloc_add(&ma,
343 &dep_list,
344 __typeof__(*dep_list),
345 pCreateInfo->dependencyCount);
346 vk_multialloc_add(&ma,
347 &flush_on_dep,
348 __typeof__(*flush_on_dep),
349 pCreateInfo->dependencyCount);
350
351 if (!vk_multialloc_zalloc2(&ma,
352 &device->vk.alloc,
353 pAllocator,
354 VK_SYSTEM_ALLOCATION_SCOPE_OBJECT)) {
355 return vk_error(device, VK_ERROR_OUT_OF_HOST_MEMORY);
356 }
357
358 vk_object_base_init(&device->vk, &pass->base, VK_OBJECT_TYPE_RENDER_PASS);
359 pass->attachment_count = pCreateInfo->attachmentCount;
360 pass->attachments = attachments;
361 pass->subpass_count = pCreateInfo->subpassCount;
362 pass->subpasses = subpasses;
363 pass->max_sample_count = 1;
364
365 /* Copy attachment descriptions. */
366 for (uint32_t i = 0; i < pass->attachment_count; i++) {
367 const VkAttachmentDescription2 *desc = &pCreateInfo->pAttachments[i];
368 struct pvr_render_pass_attachment *attachment = &pass->attachments[i];
369
370 pvr_assert(!(desc->flags & ~VK_ATTACHMENT_DESCRIPTION_MAY_ALIAS_BIT));
371
372 attachment->load_op = desc->loadOp;
373 attachment->store_op = desc->storeOp;
374
375 attachment->has_stencil = vk_format_has_stencil(attachment->vk_format);
376 if (attachment->has_stencil) {
377 attachment->stencil_load_op = desc->stencilLoadOp;
378 attachment->stencil_store_op = desc->stencilStoreOp;
379 }
380
381 attachment->vk_format = desc->format;
382 attachment->sample_count = desc->samples;
383 attachment->initial_layout = desc->initialLayout;
384 attachment->is_pbe_downscalable =
385 pvr_format_is_pbe_downscalable(attachment->vk_format);
386 attachment->index = i;
387
388 if (attachment->sample_count > pass->max_sample_count)
389 pass->max_sample_count = attachment->sample_count;
390 }
391
392 /* Count how many dependencies each subpass has. */
393 for (uint32_t i = 0; i < pCreateInfo->dependencyCount; i++) {
394 const VkSubpassDependency2 *dep = &pCreateInfo->pDependencies[i];
395
396 if (dep->srcSubpass != VK_SUBPASS_EXTERNAL &&
397 dep->dstSubpass != VK_SUBPASS_EXTERNAL &&
398 dep->srcSubpass != dep->dstSubpass) {
399 pass->subpasses[dep->dstSubpass].dep_count++;
400 }
401 }
402
403 /* Assign reference pointers to lists, and fill in the attachments list, we
404 * need to re-walk the dependencies array later to fill the per-subpass
405 * dependencies lists in.
406 */
407 for (uint32_t i = 0; i < pass->subpass_count; i++) {
408 const VkSubpassDescription2 *desc = &pCreateInfo->pSubpasses[i];
409 struct pvr_render_subpass *subpass = &pass->subpasses[i];
410
411 subpass->pipeline_bind_point = desc->pipelineBindPoint;
412 subpass->sample_count = 1;
413
414 subpass->color_count = desc->colorAttachmentCount;
415 if (subpass->color_count > 0) {
416 bool has_used_color_attachment = false;
417 uint32_t index;
418
419 subpass->color_attachments = subpass_attachments;
420 subpass_attachments += subpass->color_count;
421
422 for (uint32_t j = 0; j < subpass->color_count; j++) {
423 subpass->color_attachments[j] =
424 desc->pColorAttachments[j].attachment;
425
426 if (subpass->color_attachments[j] == VK_ATTACHMENT_UNUSED)
427 continue;
428
429 index = subpass->color_attachments[j];
430 subpass->sample_count = pass->attachments[index].sample_count;
431 has_used_color_attachment = true;
432 }
433
434 if (!has_used_color_attachment && desc->pDepthStencilAttachment &&
435 desc->pDepthStencilAttachment->attachment !=
436 VK_ATTACHMENT_UNUSED) {
437 index = desc->pDepthStencilAttachment->attachment;
438 subpass->sample_count = pass->attachments[index].sample_count;
439 }
440 }
441
442 if (desc->pResolveAttachments) {
443 subpass->resolve_attachments = subpass_attachments;
444 subpass_attachments += subpass->color_count;
445
446 for (uint32_t j = 0; j < subpass->color_count; j++) {
447 subpass->resolve_attachments[j] =
448 desc->pResolveAttachments[j].attachment;
449 }
450 }
451
452 subpass->input_count = desc->inputAttachmentCount;
453 if (subpass->input_count > 0) {
454 subpass->input_attachments = subpass_attachments;
455 subpass_attachments += subpass->input_count;
456
457 for (uint32_t j = 0; j < subpass->input_count; j++) {
458 subpass->input_attachments[j] =
459 desc->pInputAttachments[j].attachment;
460 }
461 }
462
463 if (desc->pDepthStencilAttachment) {
464 subpass->depth_stencil_attachment = subpass_attachments++;
465 *subpass->depth_stencil_attachment =
466 desc->pDepthStencilAttachment->attachment;
467 }
468
469 /* Give the dependencies a slice of the subpass_attachments array. */
470 subpass->dep_list = dep_list;
471 dep_list += subpass->dep_count;
472 subpass->flush_on_dep = flush_on_dep;
473 flush_on_dep += subpass->dep_count;
474
475 /* Reset the dependencies count so we can start from 0 and index into
476 * the dependencies array.
477 */
478 subpass->dep_count = 0;
479 subpass->index = i;
480 }
481
482 /* Compute dependencies and populate dep_list and flush_on_dep. */
483 for (uint32_t i = 0; i < pCreateInfo->dependencyCount; i++) {
484 const VkSubpassDependency2 *dep = &pCreateInfo->pDependencies[i];
485
486 if (dep->srcSubpass != VK_SUBPASS_EXTERNAL &&
487 dep->dstSubpass != VK_SUBPASS_EXTERNAL &&
488 dep->srcSubpass != dep->dstSubpass) {
489 struct pvr_render_subpass *subpass = &pass->subpasses[dep->dstSubpass];
490
491 subpass->dep_list[subpass->dep_count] = dep->srcSubpass;
492 if (pvr_subpass_has_msaa_input_attachment(subpass, pCreateInfo))
493 subpass->flush_on_dep[subpass->dep_count] = true;
494
495 subpass->dep_count++;
496 }
497 }
498
499 pass->max_tilebuffer_count =
500 PVR_SPM_LOAD_IN_BUFFERS_COUNT(&device->pdevice->dev_info);
501
502 pass->hw_setup = pvr_create_renderpass_hwsetup(device, pass, false);
503 if (!pass->hw_setup) {
504 result = vk_error(device, VK_ERROR_OUT_OF_HOST_MEMORY);
505 goto err_free_pass;
506 }
507
508 pvr_init_subpass_userpass_spawn(pass->hw_setup, pass, pass->subpasses);
509
510 for (uint32_t i = 0; i < pass->hw_setup->render_count; i++) {
511 struct pvr_renderpass_hwsetup_render *hw_render =
512 &pass->hw_setup->renders[i];
513 struct pvr_load_op *load_op = NULL;
514
515 if (hw_render->tile_buffers_count)
516 pvr_finishme("Set up tile buffer table");
517
518 if (!hw_render->color_init_count) {
519 assert(!hw_render->client_data);
520 continue;
521 }
522
523 if (!pvr_has_output_register_writes(hw_render))
524 pvr_finishme("Add output register write");
525
526 result = pvr_load_op_create(device, pAllocator, hw_render, &load_op);
527 if (result != VK_SUCCESS)
528 goto err_load_op_destroy;
529
530 hw_render->client_data = load_op;
531 }
532
533 *pRenderPass = pvr_render_pass_to_handle(pass);
534
535 return VK_SUCCESS;
536
537 err_load_op_destroy:
538 for (uint32_t i = 0; i < pass->hw_setup->render_count; i++) {
539 struct pvr_renderpass_hwsetup_render *hw_render =
540 &pass->hw_setup->renders[i];
541
542 if (hw_render->client_data)
543 pvr_load_op_destroy(device, pAllocator, hw_render->client_data);
544 }
545
546 pvr_destroy_renderpass_hwsetup(device, pass->hw_setup);
547
548 err_free_pass:
549 vk_object_base_finish(&pass->base);
550 vk_free2(&device->vk.alloc, pAllocator, pass);
551
552 return result;
553 }
554
pvr_DestroyRenderPass(VkDevice _device,VkRenderPass _pass,const VkAllocationCallbacks * pAllocator)555 void pvr_DestroyRenderPass(VkDevice _device,
556 VkRenderPass _pass,
557 const VkAllocationCallbacks *pAllocator)
558 {
559 PVR_FROM_HANDLE(pvr_device, device, _device);
560 PVR_FROM_HANDLE(pvr_render_pass, pass, _pass);
561
562 if (!pass)
563 return;
564
565 for (uint32_t i = 0; i < pass->hw_setup->render_count; i++) {
566 struct pvr_renderpass_hwsetup_render *hw_render =
567 &pass->hw_setup->renders[i];
568
569 pvr_load_op_destroy(device, pAllocator, hw_render->client_data);
570 }
571
572 pvr_destroy_renderpass_hwsetup(device, pass->hw_setup);
573 vk_object_base_finish(&pass->base);
574 vk_free2(&device->vk.alloc, pAllocator, pass);
575 }
576
pvr_GetRenderAreaGranularity(VkDevice _device,VkRenderPass renderPass,VkExtent2D * pGranularity)577 void pvr_GetRenderAreaGranularity(VkDevice _device,
578 VkRenderPass renderPass,
579 VkExtent2D *pGranularity)
580 {
581 PVR_FROM_HANDLE(pvr_device, device, _device);
582 const struct pvr_device_info *dev_info = &device->pdevice->dev_info;
583
584 /* Granularity does not depend on any settings in the render pass, so return
585 * the tile granularity.
586 *
587 * The default value is based on the minimum value found in all existing
588 * cores.
589 */
590 pGranularity->width = PVR_GET_FEATURE_VALUE(dev_info, tile_size_x, 16);
591 pGranularity->height = PVR_GET_FEATURE_VALUE(dev_info, tile_size_y, 16);
592 }
593