vg_context.c revision 438359597cd4254558f4d2fd5b54eb32c03e1b4c
1/************************************************************************** 2 * 3 * Copyright 2009 VMware, Inc. All Rights Reserved. 4 * 5 * Permission is hereby granted, free of charge, to any person obtaining a 6 * copy of this software and associated documentation files (the 7 * "Software"), to deal in the Software without restriction, including 8 * without limitation the rights to use, copy, modify, merge, publish, 9 * distribute, sub license, and/or sell copies of the Software, and to 10 * permit persons to whom the Software is furnished to do so, subject to 11 * the following conditions: 12 * 13 * The above copyright notice and this permission notice (including the 14 * next paragraph) shall be included in all copies or substantial portions 15 * of the Software. 16 * 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. 20 * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR 21 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 * 25 **************************************************************************/ 26 27#include "vg_context.h" 28 29#include "paint.h" 30#include "renderer.h" 31#include "shaders_cache.h" 32#include "shader.h" 33#include "asm_util.h" 34#include "st_inlines.h" 35#include "vg_manager.h" 36#include "api.h" 37 38#include "pipe/p_context.h" 39#include "util/u_inlines.h" 40 41#include "cso_cache/cso_context.h" 42 43#include "util/u_simple_shaders.h" 44#include "util/u_memory.h" 45#include "util/u_blit.h" 46#include "util/u_sampler.h" 47#include "util/u_math.h" 48 49struct vg_context *_vg_context = 0; 50 51struct vg_context * vg_current_context(void) 52{ 53 return _vg_context; 54} 55 56static void init_clear(struct vg_context *st) 57{ 58 struct pipe_context *pipe = st->pipe; 59 60 /* rasterizer state: bypass clipping */ 61 memset(&st->clear.raster, 0, sizeof(st->clear.raster)); 62 st->clear.raster.gl_rasterization_rules = 1; 63 64 /* fragment shader state: color pass-through program */ 65 st->clear.fs = 66 util_make_fragment_passthrough_shader(pipe); 67} 68 69/** 70 * A depth/stencil rb will be needed regardless of what the visual says. 71 */ 72static boolean 73choose_depth_stencil_format(struct vg_context *ctx) 74{ 75 struct pipe_screen *screen = ctx->pipe->screen; 76 enum pipe_format formats[] = { 77 PIPE_FORMAT_Z24_UNORM_S8_USCALED, 78 PIPE_FORMAT_S8_USCALED_Z24_UNORM, 79 PIPE_FORMAT_NONE 80 }; 81 enum pipe_format *fmt; 82 83 for (fmt = formats; *fmt != PIPE_FORMAT_NONE; fmt++) { 84 if (screen->is_format_supported(screen, *fmt, 85 PIPE_TEXTURE_2D, 0, PIPE_BIND_DEPTH_STENCIL, 0)) 86 break; 87 } 88 89 ctx->ds_format = *fmt; 90 91 return (ctx->ds_format != PIPE_FORMAT_NONE); 92} 93 94void vg_set_current_context(struct vg_context *ctx) 95{ 96 _vg_context = ctx; 97 api_make_dispatch_current((ctx) ? ctx->dispatch : NULL); 98} 99 100struct vg_context * vg_create_context(struct pipe_context *pipe, 101 const void *visual, 102 struct vg_context *share) 103{ 104 struct vg_context *ctx; 105 unsigned i; 106 107 ctx = CALLOC_STRUCT(vg_context); 108 109 ctx->pipe = pipe; 110 if (!choose_depth_stencil_format(ctx)) { 111 FREE(ctx); 112 return NULL; 113 } 114 115 ctx->dispatch = api_create_dispatch(); 116 117 vg_init_state(&ctx->state.vg); 118 ctx->state.dirty = ALL_DIRTY; 119 120 ctx->cso_context = cso_create_context(pipe); 121 122 init_clear(ctx); 123 124 ctx->default_paint = paint_create(ctx); 125 ctx->state.vg.stroke_paint = ctx->default_paint; 126 ctx->state.vg.fill_paint = ctx->default_paint; 127 128 129 ctx->mask.sampler.wrap_s = PIPE_TEX_WRAP_CLAMP_TO_EDGE; 130 ctx->mask.sampler.wrap_t = PIPE_TEX_WRAP_CLAMP_TO_EDGE; 131 ctx->mask.sampler.wrap_r = PIPE_TEX_WRAP_CLAMP_TO_EDGE; 132 ctx->mask.sampler.min_mip_filter = PIPE_TEX_MIPFILTER_NONE; 133 ctx->mask.sampler.min_img_filter = PIPE_TEX_FILTER_NEAREST; 134 ctx->mask.sampler.mag_img_filter = PIPE_TEX_FILTER_NEAREST; 135 ctx->mask.sampler.normalized_coords = 0; 136 137 ctx->blend_sampler.wrap_s = PIPE_TEX_WRAP_CLAMP_TO_EDGE; 138 ctx->blend_sampler.wrap_t = PIPE_TEX_WRAP_CLAMP_TO_EDGE; 139 ctx->blend_sampler.wrap_r = PIPE_TEX_WRAP_CLAMP_TO_EDGE; 140 ctx->blend_sampler.min_mip_filter = PIPE_TEX_MIPFILTER_NONE; 141 ctx->blend_sampler.min_img_filter = PIPE_TEX_FILTER_NEAREST; 142 ctx->blend_sampler.mag_img_filter = PIPE_TEX_FILTER_NEAREST; 143 ctx->blend_sampler.normalized_coords = 0; 144 145 for (i = 0; i < 2; i++) { 146 ctx->velems[i].src_offset = i * 4 * sizeof(float); 147 ctx->velems[i].instance_divisor = 0; 148 ctx->velems[i].vertex_buffer_index = 0; 149 ctx->velems[i].src_format = PIPE_FORMAT_R32G32B32A32_FLOAT; 150 } 151 152 vg_set_error(ctx, VG_NO_ERROR); 153 154 ctx->owned_objects[VG_OBJECT_PAINT] = cso_hash_create(); 155 ctx->owned_objects[VG_OBJECT_IMAGE] = cso_hash_create(); 156 ctx->owned_objects[VG_OBJECT_MASK] = cso_hash_create(); 157 ctx->owned_objects[VG_OBJECT_FONT] = cso_hash_create(); 158 ctx->owned_objects[VG_OBJECT_PATH] = cso_hash_create(); 159 160 ctx->renderer = renderer_create(ctx); 161 ctx->sc = shaders_cache_create(ctx); 162 ctx->shader = shader_create(ctx); 163 164 ctx->blit = util_create_blit(ctx->pipe, ctx->cso_context); 165 166 return ctx; 167} 168 169void vg_destroy_context(struct vg_context *ctx) 170{ 171 struct pipe_resource **cbuf = &ctx->mask.cbuf; 172 struct pipe_resource **vsbuf = &ctx->vs_const_buffer; 173 174 util_destroy_blit(ctx->blit); 175 renderer_destroy(ctx->renderer); 176 shaders_cache_destroy(ctx->sc); 177 shader_destroy(ctx->shader); 178 paint_destroy(ctx->default_paint); 179 180 if (*cbuf) 181 pipe_resource_reference(cbuf, NULL); 182 183 if (*vsbuf) 184 pipe_resource_reference(vsbuf, NULL); 185 186 if (ctx->clear.fs) { 187 cso_delete_fragment_shader(ctx->cso_context, ctx->clear.fs); 188 ctx->clear.fs = NULL; 189 } 190 191 if (ctx->plain_vs) { 192 vg_shader_destroy(ctx, ctx->plain_vs); 193 ctx->plain_vs = NULL; 194 } 195 if (ctx->clear_vs) { 196 vg_shader_destroy(ctx, ctx->clear_vs); 197 ctx->clear_vs = NULL; 198 } 199 if (ctx->texture_vs) { 200 vg_shader_destroy(ctx, ctx->texture_vs); 201 ctx->texture_vs = NULL; 202 } 203 204 if (ctx->pass_through_depth_fs) 205 vg_shader_destroy(ctx, ctx->pass_through_depth_fs); 206 if (ctx->mask.union_fs) 207 vg_shader_destroy(ctx, ctx->mask.union_fs); 208 if (ctx->mask.intersect_fs) 209 vg_shader_destroy(ctx, ctx->mask.intersect_fs); 210 if (ctx->mask.subtract_fs) 211 vg_shader_destroy(ctx, ctx->mask.subtract_fs); 212 if (ctx->mask.set_fs) 213 vg_shader_destroy(ctx, ctx->mask.set_fs); 214 215 cso_release_all(ctx->cso_context); 216 cso_destroy_context(ctx->cso_context); 217 218 cso_hash_delete(ctx->owned_objects[VG_OBJECT_PAINT]); 219 cso_hash_delete(ctx->owned_objects[VG_OBJECT_IMAGE]); 220 cso_hash_delete(ctx->owned_objects[VG_OBJECT_MASK]); 221 cso_hash_delete(ctx->owned_objects[VG_OBJECT_FONT]); 222 cso_hash_delete(ctx->owned_objects[VG_OBJECT_PATH]); 223 224 api_destroy_dispatch(ctx->dispatch); 225 226 FREE(ctx); 227} 228 229void vg_init_object(struct vg_object *obj, struct vg_context *ctx, enum vg_object_type type) 230{ 231 obj->type = type; 232 obj->ctx = ctx; 233} 234 235VGboolean vg_context_is_object_valid(struct vg_context *ctx, 236 enum vg_object_type type, 237 void *ptr) 238{ 239 if (ctx) { 240 struct cso_hash *hash = ctx->owned_objects[type]; 241 if (!hash) 242 return VG_FALSE; 243 return cso_hash_contains(hash, (unsigned)(long)ptr); 244 } 245 return VG_FALSE; 246} 247 248void vg_context_add_object(struct vg_context *ctx, 249 enum vg_object_type type, 250 void *ptr) 251{ 252 if (ctx) { 253 struct cso_hash *hash = ctx->owned_objects[type]; 254 if (!hash) 255 return; 256 cso_hash_insert(hash, (unsigned)(long)ptr, ptr); 257 } 258} 259 260void vg_context_remove_object(struct vg_context *ctx, 261 enum vg_object_type type, 262 void *ptr) 263{ 264 if (ctx) { 265 struct cso_hash *hash = ctx->owned_objects[type]; 266 if (!hash) 267 return; 268 cso_hash_take(hash, (unsigned)(long)ptr); 269 } 270} 271 272static void update_clip_state(struct vg_context *ctx) 273{ 274 struct pipe_depth_stencil_alpha_state *dsa = &ctx->state.g3d.dsa; 275 struct vg_state *state = &ctx->state.vg; 276 277 memset(dsa, 0, sizeof(struct pipe_depth_stencil_alpha_state)); 278 279 if (state->scissoring) { 280 struct pipe_framebuffer_state *fb = &ctx->state.g3d.fb; 281 int i; 282 283 renderer_scissor_begin(ctx->renderer, VG_FALSE); 284 285 for (i = 0; i < state->scissor_rects_num; ++i) { 286 const float x = state->scissor_rects[i * 4 + 0].f; 287 const float y = state->scissor_rects[i * 4 + 1].f; 288 const float width = state->scissor_rects[i * 4 + 2].f; 289 const float height = state->scissor_rects[i * 4 + 3].f; 290 VGint x0, y0, x1, y1, iw, ih; 291 292 x0 = (VGint) x; 293 y0 = (VGint) y; 294 if (x0 < 0) 295 x0 = 0; 296 if (y0 < 0) 297 y0 = 0; 298 299 /* note that x1 and y1 are exclusive */ 300 x1 = (VGint) ceilf(x + width); 301 y1 = (VGint) ceilf(y + height); 302 if (x1 > fb->width) 303 x1 = fb->width; 304 if (y1 > fb->height) 305 y1 = fb->height; 306 307 iw = x1 - x0; 308 ih = y1 - y0; 309 if (iw > 0 && ih> 0 ) 310 renderer_scissor(ctx->renderer, x0, y0, iw, ih); 311 } 312 313 renderer_scissor_end(ctx->renderer); 314 315 dsa->depth.enabled = 1; /* glEnable(GL_DEPTH_TEST); */ 316 dsa->depth.writemask = 0;/*glDepthMask(FALSE);*/ 317 dsa->depth.func = PIPE_FUNC_GEQUAL; 318 } 319} 320 321void vg_validate_state(struct vg_context *ctx) 322{ 323 vg_manager_validate_framebuffer(ctx); 324 325 if ((ctx->state.dirty & BLEND_DIRTY)) { 326 struct pipe_blend_state *blend = &ctx->state.g3d.blend; 327 memset(blend, 0, sizeof(struct pipe_blend_state)); 328 blend->rt[0].blend_enable = 1; 329 blend->rt[0].colormask = PIPE_MASK_RGBA; 330 331 switch (ctx->state.vg.blend_mode) { 332 case VG_BLEND_SRC: 333 blend->rt[0].rgb_src_factor = PIPE_BLENDFACTOR_ONE; 334 blend->rt[0].alpha_src_factor = PIPE_BLENDFACTOR_ONE; 335 blend->rt[0].rgb_dst_factor = PIPE_BLENDFACTOR_ZERO; 336 blend->rt[0].alpha_dst_factor = PIPE_BLENDFACTOR_ZERO; 337 blend->rt[0].blend_enable = 0; 338 break; 339 case VG_BLEND_SRC_OVER: 340 blend->rt[0].rgb_src_factor = PIPE_BLENDFACTOR_SRC_ALPHA; 341 blend->rt[0].alpha_src_factor = PIPE_BLENDFACTOR_ONE; 342 blend->rt[0].rgb_dst_factor = PIPE_BLENDFACTOR_INV_SRC_ALPHA; 343 blend->rt[0].alpha_dst_factor = PIPE_BLENDFACTOR_INV_SRC_ALPHA; 344 break; 345 case VG_BLEND_DST_OVER: 346 blend->rt[0].rgb_src_factor = PIPE_BLENDFACTOR_INV_DST_ALPHA; 347 blend->rt[0].alpha_src_factor = PIPE_BLENDFACTOR_INV_DST_ALPHA; 348 blend->rt[0].rgb_dst_factor = PIPE_BLENDFACTOR_DST_ALPHA; 349 blend->rt[0].alpha_dst_factor = PIPE_BLENDFACTOR_DST_ALPHA; 350 break; 351 case VG_BLEND_SRC_IN: 352 blend->rt[0].rgb_src_factor = PIPE_BLENDFACTOR_DST_ALPHA; 353 blend->rt[0].alpha_src_factor = PIPE_BLENDFACTOR_DST_ALPHA; 354 blend->rt[0].rgb_dst_factor = PIPE_BLENDFACTOR_ZERO; 355 blend->rt[0].alpha_dst_factor = PIPE_BLENDFACTOR_ZERO; 356 break; 357 case VG_BLEND_DST_IN: 358 blend->rt[0].rgb_src_factor = PIPE_BLENDFACTOR_ZERO; 359 blend->rt[0].alpha_src_factor = PIPE_BLENDFACTOR_ZERO; 360 blend->rt[0].rgb_dst_factor = PIPE_BLENDFACTOR_SRC_ALPHA; 361 blend->rt[0].alpha_dst_factor = PIPE_BLENDFACTOR_SRC_ALPHA; 362 break; 363 case VG_BLEND_MULTIPLY: 364 case VG_BLEND_SCREEN: 365 case VG_BLEND_DARKEN: 366 case VG_BLEND_LIGHTEN: 367 blend->rt[0].rgb_src_factor = PIPE_BLENDFACTOR_ONE; 368 blend->rt[0].alpha_src_factor = PIPE_BLENDFACTOR_ONE; 369 blend->rt[0].rgb_dst_factor = PIPE_BLENDFACTOR_ZERO; 370 blend->rt[0].alpha_dst_factor = PIPE_BLENDFACTOR_ZERO; 371 blend->rt[0].blend_enable = 0; 372 break; 373 case VG_BLEND_ADDITIVE: 374 blend->rt[0].rgb_src_factor = PIPE_BLENDFACTOR_ONE; 375 blend->rt[0].alpha_src_factor = PIPE_BLENDFACTOR_ONE; 376 blend->rt[0].rgb_dst_factor = PIPE_BLENDFACTOR_ONE; 377 blend->rt[0].alpha_dst_factor = PIPE_BLENDFACTOR_ONE; 378 break; 379 default: 380 assert(!"not implemented blend mode"); 381 } 382 cso_set_blend(ctx->cso_context, &ctx->state.g3d.blend); 383 } 384 if ((ctx->state.dirty & RASTERIZER_DIRTY)) { 385 struct pipe_rasterizer_state *raster = &ctx->state.g3d.rasterizer; 386 memset(raster, 0, sizeof(struct pipe_rasterizer_state)); 387 raster->gl_rasterization_rules = 1; 388 cso_set_rasterizer(ctx->cso_context, &ctx->state.g3d.rasterizer); 389 } 390 if ((ctx->state.dirty & FRAMEBUFFER_DIRTY)) { 391 struct pipe_framebuffer_state *fb = &ctx->state.g3d.fb; 392 struct pipe_resource **cbuf = &ctx->vs_const_buffer; 393 VGfloat vs_consts[8]; 394 395 memset(fb, 0, sizeof(struct pipe_framebuffer_state)); 396 fb->width = ctx->draw_buffer->width; 397 fb->height = ctx->draw_buffer->height; 398 fb->nr_cbufs = 1; 399 fb->cbufs[0] = ctx->draw_buffer->strb->surface; 400 fb->zsbuf = ctx->draw_buffer->dsrb->surface; 401 402 cso_set_framebuffer(ctx->cso_context, fb); 403 vg_set_viewport(ctx, VEGA_Y0_BOTTOM); 404 405 /* surface coordinates to clipped coordinates */ 406 vs_consts[0] = 2.0f / fb->width; 407 vs_consts[1] = 2.0f / fb->height; 408 vs_consts[2] = 1.0f; 409 vs_consts[3] = 1.0f; 410 vs_consts[4] = -1.0f; 411 vs_consts[5] = -1.0f; 412 vs_consts[6] = 0.0f; 413 vs_consts[7] = 0.0f; 414 415 pipe_resource_reference(cbuf, NULL); 416 *cbuf = pipe_buffer_create(ctx->pipe->screen, 417 PIPE_BIND_CONSTANT_BUFFER, 418 sizeof(vs_consts)); 419 420 if (*cbuf) { 421 st_no_flush_pipe_buffer_write(ctx, *cbuf, 422 0, sizeof(vs_consts), vs_consts); 423 } 424 ctx->pipe->set_constant_buffer(ctx->pipe, PIPE_SHADER_VERTEX, 0, *cbuf); 425 426 /* we also got a new depth buffer */ 427 if ((ctx->state.dirty & DEPTH_STENCIL_DIRTY)) 428 ctx->pipe->clear(ctx->pipe, PIPE_CLEAR_DEPTHSTENCIL, NULL, 0.0, 0); 429 } 430 if ((ctx->state.dirty & VS_DIRTY)) { 431 cso_set_vertex_shader_handle(ctx->cso_context, 432 vg_plain_vs(ctx)); 433 } 434 435 /* must be last because it renders to the depth buffer*/ 436 if ((ctx->state.dirty & DEPTH_STENCIL_DIRTY)) { 437 update_clip_state(ctx); 438 cso_set_depth_stencil_alpha(ctx->cso_context, &ctx->state.g3d.dsa); 439 } 440 441 shader_set_masking(ctx->shader, ctx->state.vg.masking); 442 shader_set_image_mode(ctx->shader, ctx->state.vg.image_mode); 443 444 ctx->state.dirty = NONE_DIRTY; 445} 446 447VGboolean vg_object_is_valid(void *ptr, enum vg_object_type type) 448{ 449 struct vg_object *obj = ptr; 450 if (ptr && is_aligned(obj) && obj->type == type) 451 return VG_TRUE; 452 else 453 return VG_FALSE; 454} 455 456void vg_set_error(struct vg_context *ctx, 457 VGErrorCode code) 458{ 459 /*vgGetError returns the oldest error code provided by 460 * an API call on the current context since the previous 461 * call to vgGetError on that context (or since the creation 462 of the context).*/ 463 if (ctx->_error == VG_NO_ERROR) 464 ctx->_error = code; 465} 466 467void vg_prepare_blend_surface(struct vg_context *ctx) 468{ 469 struct pipe_surface *dest_surface = NULL; 470 struct pipe_context *pipe = ctx->pipe; 471 struct pipe_sampler_view *view; 472 struct pipe_sampler_view view_templ; 473 struct st_framebuffer *stfb = ctx->draw_buffer; 474 struct st_renderbuffer *strb = stfb->strb; 475 476 /* first finish all pending rendering */ 477 vgFinish(); 478 479 u_sampler_view_default_template(&view_templ, strb->texture, strb->texture->format); 480 view = pipe->create_sampler_view(pipe, strb->texture, &view_templ); 481 482 dest_surface = pipe->screen->get_tex_surface(pipe->screen, 483 stfb->blend_texture_view->texture, 484 0, 0, 0, 485 PIPE_BIND_RENDER_TARGET); 486 util_blit_pixels_tex(ctx->blit, 487 view, 488 0, 0, 489 strb->width, strb->height, 490 dest_surface, 491 0, 0, 492 strb->width, strb->height, 493 0.0, PIPE_TEX_MIPFILTER_NEAREST); 494 495 if (dest_surface) 496 pipe_surface_reference(&dest_surface, NULL); 497 498 /* make sure it's complete */ 499 vgFinish(); 500 501 pipe_sampler_view_reference(&view, NULL); 502} 503 504 505void vg_prepare_blend_surface_from_mask(struct vg_context *ctx) 506{ 507 struct pipe_surface *dest_surface = NULL; 508 struct pipe_context *pipe = ctx->pipe; 509 struct st_framebuffer *stfb = ctx->draw_buffer; 510 struct st_renderbuffer *strb = stfb->strb; 511 512 vg_validate_state(ctx); 513 514 /* first finish all pending rendering */ 515 vgFinish(); 516 517 dest_surface = pipe->screen->get_tex_surface(pipe->screen, 518 stfb->blend_texture_view->texture, 519 0, 0, 0, 520 PIPE_BIND_RENDER_TARGET); 521 522 /* flip it, because we want to use it as a sampler */ 523 util_blit_pixels_tex(ctx->blit, 524 stfb->alpha_mask_view, 525 0, strb->height, 526 strb->width, 0, 527 dest_surface, 528 0, 0, 529 strb->width, strb->height, 530 0.0, PIPE_TEX_MIPFILTER_NEAREST); 531 532 /* make sure it's complete */ 533 vgFinish(); 534 535 if (dest_surface) 536 pipe_surface_reference(&dest_surface, NULL); 537} 538 539void * vg_plain_vs(struct vg_context *ctx) 540{ 541 if (!ctx->plain_vs) { 542 ctx->plain_vs = shader_create_from_text(ctx->pipe, 543 vs_plain_asm, 544 200, 545 PIPE_SHADER_VERTEX); 546 } 547 548 return ctx->plain_vs->driver; 549} 550 551 552void * vg_clear_vs(struct vg_context *ctx) 553{ 554 if (!ctx->clear_vs) { 555 ctx->clear_vs = shader_create_from_text(ctx->pipe, 556 vs_clear_asm, 557 200, 558 PIPE_SHADER_VERTEX); 559 } 560 561 return ctx->clear_vs->driver; 562} 563 564void * vg_texture_vs(struct vg_context *ctx) 565{ 566 if (!ctx->texture_vs) { 567 ctx->texture_vs = shader_create_from_text(ctx->pipe, 568 vs_texture_asm, 569 200, 570 PIPE_SHADER_VERTEX); 571 } 572 573 return ctx->texture_vs->driver; 574} 575 576void vg_set_viewport(struct vg_context *ctx, VegaOrientation orientation) 577{ 578 struct pipe_viewport_state viewport; 579 struct pipe_framebuffer_state *fb = &ctx->state.g3d.fb; 580 VGfloat y_scale = (orientation == VEGA_Y0_BOTTOM) ? -2.f : 2.f; 581 582 viewport.scale[0] = fb->width / 2.f; 583 viewport.scale[1] = fb->height / y_scale; 584 viewport.scale[2] = 1.0; 585 viewport.scale[3] = 1.0; 586 viewport.translate[0] = fb->width / 2.f; 587 viewport.translate[1] = fb->height / 2.f; 588 viewport.translate[2] = 0.0; 589 viewport.translate[3] = 0.0; 590 591 cso_set_viewport(ctx->cso_context, &viewport); 592} 593