vg_context.c revision 93a7e6d94e09a25bdbe31eedb0759e390ccb6a86
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 48struct vg_context *_vg_context = 0; 49 50struct vg_context * vg_current_context(void) 51{ 52 return _vg_context; 53} 54 55static void init_clear(struct vg_context *st) 56{ 57 struct pipe_context *pipe = st->pipe; 58 59 /* rasterizer state: bypass clipping */ 60 memset(&st->clear.raster, 0, sizeof(st->clear.raster)); 61 st->clear.raster.gl_rasterization_rules = 1; 62 63 /* fragment shader state: color pass-through program */ 64 st->clear.fs = 65 util_make_fragment_passthrough_shader(pipe); 66} 67 68/** 69 * A depth/stencil rb will be needed regardless of what the visual says. 70 */ 71static boolean 72choose_depth_stencil_format(struct vg_context *ctx) 73{ 74 struct pipe_screen *screen = ctx->pipe->screen; 75 enum pipe_format formats[] = { 76 PIPE_FORMAT_Z24_UNORM_S8_USCALED, 77 PIPE_FORMAT_S8_USCALED_Z24_UNORM, 78 PIPE_FORMAT_NONE 79 }; 80 enum pipe_format *fmt; 81 82 for (fmt = formats; *fmt != PIPE_FORMAT_NONE; fmt++) { 83 if (screen->is_format_supported(screen, *fmt, 84 PIPE_TEXTURE_2D, 0, PIPE_BIND_DEPTH_STENCIL, 0)) 85 break; 86 } 87 88 ctx->ds_format = *fmt; 89 90 return (ctx->ds_format != PIPE_FORMAT_NONE); 91} 92 93void vg_set_current_context(struct vg_context *ctx) 94{ 95 _vg_context = ctx; 96 api_make_dispatch_current((ctx) ? ctx->dispatch : NULL); 97} 98 99struct vg_context * vg_create_context(struct pipe_context *pipe, 100 const void *visual, 101 struct vg_context *share) 102{ 103 struct vg_context *ctx; 104 unsigned i; 105 106 ctx = CALLOC_STRUCT(vg_context); 107 108 ctx->pipe = pipe; 109 if (!choose_depth_stencil_format(ctx)) { 110 FREE(ctx); 111 return NULL; 112 } 113 114 ctx->dispatch = api_create_dispatch(); 115 116 vg_init_state(&ctx->state.vg); 117 ctx->state.dirty = ALL_DIRTY; 118 119 ctx->cso_context = cso_create_context(pipe); 120 121 init_clear(ctx); 122 123 ctx->default_paint = paint_create(ctx); 124 ctx->state.vg.stroke_paint = ctx->default_paint; 125 ctx->state.vg.fill_paint = ctx->default_paint; 126 127 128 ctx->mask.sampler.wrap_s = PIPE_TEX_WRAP_CLAMP_TO_EDGE; 129 ctx->mask.sampler.wrap_t = PIPE_TEX_WRAP_CLAMP_TO_EDGE; 130 ctx->mask.sampler.min_mip_filter = PIPE_TEX_MIPFILTER_NONE; 131 ctx->mask.sampler.min_img_filter = PIPE_TEX_FILTER_NEAREST; 132 ctx->mask.sampler.mag_img_filter = PIPE_TEX_FILTER_NEAREST; 133 ctx->mask.sampler.normalized_coords = 0; 134 135 ctx->blend_sampler.wrap_s = PIPE_TEX_WRAP_CLAMP_TO_EDGE; 136 ctx->blend_sampler.wrap_t = PIPE_TEX_WRAP_CLAMP_TO_EDGE; 137 ctx->blend_sampler.min_mip_filter = PIPE_TEX_MIPFILTER_NONE; 138 ctx->blend_sampler.min_img_filter = PIPE_TEX_FILTER_NEAREST; 139 ctx->blend_sampler.mag_img_filter = PIPE_TEX_FILTER_NEAREST; 140 ctx->blend_sampler.normalized_coords = 0; 141 142 for (i = 0; i < 2; i++) { 143 ctx->velems[i].src_offset = i * 4 * sizeof(float); 144 ctx->velems[i].instance_divisor = 0; 145 ctx->velems[i].vertex_buffer_index = 0; 146 ctx->velems[i].src_format = PIPE_FORMAT_R32G32B32A32_FLOAT; 147 } 148 149 vg_set_error(ctx, VG_NO_ERROR); 150 151 ctx->owned_objects[VG_OBJECT_PAINT] = cso_hash_create(); 152 ctx->owned_objects[VG_OBJECT_IMAGE] = cso_hash_create(); 153 ctx->owned_objects[VG_OBJECT_MASK] = cso_hash_create(); 154 ctx->owned_objects[VG_OBJECT_FONT] = cso_hash_create(); 155 ctx->owned_objects[VG_OBJECT_PATH] = cso_hash_create(); 156 157 ctx->renderer = renderer_create(ctx); 158 ctx->sc = shaders_cache_create(ctx); 159 ctx->shader = shader_create(ctx); 160 161 ctx->blit = util_create_blit(ctx->pipe, ctx->cso_context); 162 163 return ctx; 164} 165 166void vg_destroy_context(struct vg_context *ctx) 167{ 168 struct pipe_resource **cbuf = &ctx->mask.cbuf; 169 struct pipe_resource **vsbuf = &ctx->vs_const_buffer; 170 171 util_destroy_blit(ctx->blit); 172 renderer_destroy(ctx->renderer); 173 shaders_cache_destroy(ctx->sc); 174 shader_destroy(ctx->shader); 175 paint_destroy(ctx->default_paint); 176 177 if (*cbuf) 178 pipe_resource_reference(cbuf, NULL); 179 180 if (*vsbuf) 181 pipe_resource_reference(vsbuf, NULL); 182 183 if (ctx->clear.fs) { 184 cso_delete_fragment_shader(ctx->cso_context, ctx->clear.fs); 185 ctx->clear.fs = NULL; 186 } 187 188 if (ctx->plain_vs) { 189 vg_shader_destroy(ctx, ctx->plain_vs); 190 ctx->plain_vs = NULL; 191 } 192 if (ctx->clear_vs) { 193 vg_shader_destroy(ctx, ctx->clear_vs); 194 ctx->clear_vs = NULL; 195 } 196 if (ctx->texture_vs) { 197 vg_shader_destroy(ctx, ctx->texture_vs); 198 ctx->texture_vs = NULL; 199 } 200 201 if (ctx->pass_through_depth_fs) 202 vg_shader_destroy(ctx, ctx->pass_through_depth_fs); 203 if (ctx->mask.union_fs) 204 vg_shader_destroy(ctx, ctx->mask.union_fs); 205 if (ctx->mask.intersect_fs) 206 vg_shader_destroy(ctx, ctx->mask.intersect_fs); 207 if (ctx->mask.subtract_fs) 208 vg_shader_destroy(ctx, ctx->mask.subtract_fs); 209 if (ctx->mask.set_fs) 210 vg_shader_destroy(ctx, ctx->mask.set_fs); 211 212 cso_release_all(ctx->cso_context); 213 cso_destroy_context(ctx->cso_context); 214 215 cso_hash_delete(ctx->owned_objects[VG_OBJECT_PAINT]); 216 cso_hash_delete(ctx->owned_objects[VG_OBJECT_IMAGE]); 217 cso_hash_delete(ctx->owned_objects[VG_OBJECT_MASK]); 218 cso_hash_delete(ctx->owned_objects[VG_OBJECT_FONT]); 219 cso_hash_delete(ctx->owned_objects[VG_OBJECT_PATH]); 220 221 api_destroy_dispatch(ctx->dispatch); 222 223 FREE(ctx); 224} 225 226void vg_init_object(struct vg_object *obj, struct vg_context *ctx, enum vg_object_type type) 227{ 228 obj->type = type; 229 obj->ctx = ctx; 230} 231 232VGboolean vg_context_is_object_valid(struct vg_context *ctx, 233 enum vg_object_type type, 234 void *ptr) 235{ 236 if (ctx) { 237 struct cso_hash *hash = ctx->owned_objects[type]; 238 if (!hash) 239 return VG_FALSE; 240 return cso_hash_contains(hash, (unsigned)(long)ptr); 241 } 242 return VG_FALSE; 243} 244 245void vg_context_add_object(struct vg_context *ctx, 246 enum vg_object_type type, 247 void *ptr) 248{ 249 if (ctx) { 250 struct cso_hash *hash = ctx->owned_objects[type]; 251 if (!hash) 252 return; 253 cso_hash_insert(hash, (unsigned)(long)ptr, ptr); 254 } 255} 256 257void vg_context_remove_object(struct vg_context *ctx, 258 enum vg_object_type type, 259 void *ptr) 260{ 261 if (ctx) { 262 struct cso_hash *hash = ctx->owned_objects[type]; 263 if (!hash) 264 return; 265 cso_hash_take(hash, (unsigned)(long)ptr); 266 } 267} 268 269static void update_clip_state(struct vg_context *ctx) 270{ 271 struct pipe_depth_stencil_alpha_state *dsa = &ctx->state.g3d.dsa; 272 struct vg_state *state = &ctx->state.vg; 273 274 memset(dsa, 0, sizeof(struct pipe_depth_stencil_alpha_state)); 275 276 if (state->scissoring) { 277 struct pipe_blend_state *blend = &ctx->state.g3d.blend; 278 struct pipe_framebuffer_state *fb = &ctx->state.g3d.fb; 279 int i; 280 281 dsa->depth.writemask = 1;/*glDepthMask(TRUE);*/ 282 dsa->depth.func = PIPE_FUNC_ALWAYS; 283 dsa->depth.enabled = 1; 284 285 cso_save_blend(ctx->cso_context); 286 cso_save_fragment_shader(ctx->cso_context); 287 /* set a passthrough shader */ 288 if (!ctx->pass_through_depth_fs) 289 ctx->pass_through_depth_fs = shader_create_from_text(ctx->pipe, 290 pass_through_depth_asm, 291 40, 292 PIPE_SHADER_FRAGMENT); 293 cso_set_fragment_shader_handle(ctx->cso_context, 294 ctx->pass_through_depth_fs->driver); 295 cso_set_depth_stencil_alpha(ctx->cso_context, dsa); 296 297 ctx->pipe->clear(ctx->pipe, PIPE_CLEAR_DEPTHSTENCIL, NULL, 1.0, 0); 298 299 /* disable color writes */ 300 blend->rt[0].colormask = 0; /*disable colorwrites*/ 301 cso_set_blend(ctx->cso_context, blend); 302 303 /* enable scissoring */ 304 for (i = 0; i < state->scissor_rects_num; ++i) { 305 const float x = state->scissor_rects[i * 4 + 0].f; 306 const float y = state->scissor_rects[i * 4 + 1].f; 307 const float width = state->scissor_rects[i * 4 + 2].f; 308 const float height = state->scissor_rects[i * 4 + 3].f; 309 VGfloat minx, miny, maxx, maxy; 310 311 minx = 0; 312 miny = 0; 313 maxx = fb->width; 314 maxy = fb->height; 315 316 if (x > minx) 317 minx = x; 318 if (y > miny) 319 miny = y; 320 321 if (x + width < maxx) 322 maxx = x + width; 323 if (y + height < maxy) 324 maxy = y + height; 325 326 /* check for null space */ 327 if (minx >= maxx || miny >= maxy) 328 minx = miny = maxx = maxy = 0; 329 330 /*glClear(GL_DEPTH_BUFFER_BIT);*/ 331 renderer_draw_quad(ctx->renderer, minx, miny, maxx, maxy, 0.0f); 332 } 333 334 cso_restore_blend(ctx->cso_context); 335 cso_restore_fragment_shader(ctx->cso_context); 336 337 dsa->depth.enabled = 1; /* glEnable(GL_DEPTH_TEST); */ 338 dsa->depth.writemask = 0;/*glDepthMask(FALSE);*/ 339 dsa->depth.func = PIPE_FUNC_GEQUAL; 340 } 341} 342 343void vg_validate_state(struct vg_context *ctx) 344{ 345 vg_manager_validate_framebuffer(ctx); 346 347 if ((ctx->state.dirty & BLEND_DIRTY)) { 348 struct pipe_blend_state *blend = &ctx->state.g3d.blend; 349 memset(blend, 0, sizeof(struct pipe_blend_state)); 350 blend->rt[0].blend_enable = 1; 351 blend->rt[0].colormask = PIPE_MASK_RGBA; 352 353 switch (ctx->state.vg.blend_mode) { 354 case VG_BLEND_SRC: 355 blend->rt[0].rgb_src_factor = PIPE_BLENDFACTOR_ONE; 356 blend->rt[0].alpha_src_factor = PIPE_BLENDFACTOR_ONE; 357 blend->rt[0].rgb_dst_factor = PIPE_BLENDFACTOR_ZERO; 358 blend->rt[0].alpha_dst_factor = PIPE_BLENDFACTOR_ZERO; 359 blend->rt[0].blend_enable = 0; 360 break; 361 case VG_BLEND_SRC_OVER: 362 blend->rt[0].rgb_src_factor = PIPE_BLENDFACTOR_SRC_ALPHA; 363 blend->rt[0].alpha_src_factor = PIPE_BLENDFACTOR_ONE; 364 blend->rt[0].rgb_dst_factor = PIPE_BLENDFACTOR_INV_SRC_ALPHA; 365 blend->rt[0].alpha_dst_factor = PIPE_BLENDFACTOR_INV_SRC_ALPHA; 366 break; 367 case VG_BLEND_DST_OVER: 368 blend->rt[0].rgb_src_factor = PIPE_BLENDFACTOR_INV_DST_ALPHA; 369 blend->rt[0].alpha_src_factor = PIPE_BLENDFACTOR_INV_DST_ALPHA; 370 blend->rt[0].rgb_dst_factor = PIPE_BLENDFACTOR_DST_ALPHA; 371 blend->rt[0].alpha_dst_factor = PIPE_BLENDFACTOR_DST_ALPHA; 372 break; 373 case VG_BLEND_SRC_IN: 374 blend->rt[0].rgb_src_factor = PIPE_BLENDFACTOR_DST_ALPHA; 375 blend->rt[0].alpha_src_factor = PIPE_BLENDFACTOR_DST_ALPHA; 376 blend->rt[0].rgb_dst_factor = PIPE_BLENDFACTOR_ZERO; 377 blend->rt[0].alpha_dst_factor = PIPE_BLENDFACTOR_ZERO; 378 break; 379 case VG_BLEND_DST_IN: 380 blend->rt[0].rgb_src_factor = PIPE_BLENDFACTOR_ZERO; 381 blend->rt[0].alpha_src_factor = PIPE_BLENDFACTOR_ZERO; 382 blend->rt[0].rgb_dst_factor = PIPE_BLENDFACTOR_SRC_ALPHA; 383 blend->rt[0].alpha_dst_factor = PIPE_BLENDFACTOR_SRC_ALPHA; 384 break; 385 case VG_BLEND_MULTIPLY: 386 case VG_BLEND_SCREEN: 387 case VG_BLEND_DARKEN: 388 case VG_BLEND_LIGHTEN: 389 blend->rt[0].rgb_src_factor = PIPE_BLENDFACTOR_ONE; 390 blend->rt[0].alpha_src_factor = PIPE_BLENDFACTOR_ONE; 391 blend->rt[0].rgb_dst_factor = PIPE_BLENDFACTOR_ZERO; 392 blend->rt[0].alpha_dst_factor = PIPE_BLENDFACTOR_ZERO; 393 blend->rt[0].blend_enable = 0; 394 break; 395 case VG_BLEND_ADDITIVE: 396 blend->rt[0].rgb_src_factor = PIPE_BLENDFACTOR_ONE; 397 blend->rt[0].alpha_src_factor = PIPE_BLENDFACTOR_ONE; 398 blend->rt[0].rgb_dst_factor = PIPE_BLENDFACTOR_ONE; 399 blend->rt[0].alpha_dst_factor = PIPE_BLENDFACTOR_ONE; 400 break; 401 default: 402 assert(!"not implemented blend mode"); 403 } 404 cso_set_blend(ctx->cso_context, &ctx->state.g3d.blend); 405 } 406 if ((ctx->state.dirty & RASTERIZER_DIRTY)) { 407 struct pipe_rasterizer_state *raster = &ctx->state.g3d.rasterizer; 408 memset(raster, 0, sizeof(struct pipe_rasterizer_state)); 409 raster->gl_rasterization_rules = 1; 410 cso_set_rasterizer(ctx->cso_context, &ctx->state.g3d.rasterizer); 411 } 412 if ((ctx->state.dirty & VIEWPORT_DIRTY)) { 413 struct pipe_framebuffer_state *fb = &ctx->state.g3d.fb; 414 const VGint param_bytes = 8 * sizeof(VGfloat); 415 VGfloat vs_consts[8] = { 416 2.f/fb->width, 2.f/fb->height, 1, 1, 417 -1, -1, 0, 0 418 }; 419 struct pipe_resource **cbuf = &ctx->vs_const_buffer; 420 421 vg_set_viewport(ctx, VEGA_Y0_BOTTOM); 422 423 pipe_resource_reference(cbuf, NULL); 424 *cbuf = pipe_buffer_create(ctx->pipe->screen, 425 PIPE_BIND_CONSTANT_BUFFER, 426 param_bytes); 427 428 if (*cbuf) { 429 st_no_flush_pipe_buffer_write(ctx, *cbuf, 430 0, param_bytes, vs_consts); 431 } 432 ctx->pipe->set_constant_buffer(ctx->pipe, PIPE_SHADER_VERTEX, 0, *cbuf); 433 } 434 if ((ctx->state.dirty & VS_DIRTY)) { 435 cso_set_vertex_shader_handle(ctx->cso_context, 436 vg_plain_vs(ctx)); 437 } 438 439 /* must be last because it renders to the depth buffer*/ 440 if ((ctx->state.dirty & DEPTH_STENCIL_DIRTY)) { 441 update_clip_state(ctx); 442 cso_set_depth_stencil_alpha(ctx->cso_context, &ctx->state.g3d.dsa); 443 } 444 445 shader_set_masking(ctx->shader, ctx->state.vg.masking); 446 shader_set_image_mode(ctx->shader, ctx->state.vg.image_mode); 447 448 ctx->state.dirty = NONE_DIRTY; 449} 450 451VGboolean vg_object_is_valid(void *ptr, enum vg_object_type type) 452{ 453 struct vg_object *obj = ptr; 454 if (ptr && is_aligned(obj) && obj->type == type) 455 return VG_TRUE; 456 else 457 return VG_FALSE; 458} 459 460void vg_set_error(struct vg_context *ctx, 461 VGErrorCode code) 462{ 463 /*vgGetError returns the oldest error code provided by 464 * an API call on the current context since the previous 465 * call to vgGetError on that context (or since the creation 466 of the context).*/ 467 if (ctx->_error == VG_NO_ERROR) 468 ctx->_error = code; 469} 470 471void vg_prepare_blend_surface(struct vg_context *ctx) 472{ 473 struct pipe_surface *dest_surface = NULL; 474 struct pipe_context *pipe = ctx->pipe; 475 struct pipe_sampler_view *view; 476 struct pipe_sampler_view view_templ; 477 struct st_framebuffer *stfb = ctx->draw_buffer; 478 struct st_renderbuffer *strb = stfb->strb; 479 480 /* first finish all pending rendering */ 481 vgFinish(); 482 483 u_sampler_view_default_template(&view_templ, strb->texture, strb->texture->format); 484 view = pipe->create_sampler_view(pipe, strb->texture, &view_templ); 485 486 dest_surface = pipe->screen->get_tex_surface(pipe->screen, 487 stfb->blend_texture_view->texture, 488 0, 0, 0, 489 PIPE_BIND_RENDER_TARGET); 490 /* flip it, because we want to use it as a sampler */ 491 util_blit_pixels_tex(ctx->blit, 492 view, 493 0, strb->height, 494 strb->width, 0, 495 dest_surface, 496 0, 0, 497 strb->width, strb->height, 498 0.0, PIPE_TEX_MIPFILTER_NEAREST); 499 500 if (dest_surface) 501 pipe_surface_reference(&dest_surface, NULL); 502 503 /* make sure it's complete */ 504 vgFinish(); 505 506 pipe_sampler_view_reference(&view, NULL); 507} 508 509 510void vg_prepare_blend_surface_from_mask(struct vg_context *ctx) 511{ 512 struct pipe_surface *dest_surface = NULL; 513 struct pipe_context *pipe = ctx->pipe; 514 struct st_framebuffer *stfb = ctx->draw_buffer; 515 struct st_renderbuffer *strb = stfb->strb; 516 517 vg_validate_state(ctx); 518 519 /* first finish all pending rendering */ 520 vgFinish(); 521 522 dest_surface = pipe->screen->get_tex_surface(pipe->screen, 523 stfb->blend_texture_view->texture, 524 0, 0, 0, 525 PIPE_BIND_RENDER_TARGET); 526 527 /* flip it, because we want to use it as a sampler */ 528 util_blit_pixels_tex(ctx->blit, 529 stfb->alpha_mask_view, 530 0, strb->height, 531 strb->width, 0, 532 dest_surface, 533 0, 0, 534 strb->width, strb->height, 535 0.0, PIPE_TEX_MIPFILTER_NEAREST); 536 537 /* make sure it's complete */ 538 vgFinish(); 539 540 if (dest_surface) 541 pipe_surface_reference(&dest_surface, NULL); 542} 543 544void * vg_plain_vs(struct vg_context *ctx) 545{ 546 if (!ctx->plain_vs) { 547 ctx->plain_vs = shader_create_from_text(ctx->pipe, 548 vs_plain_asm, 549 200, 550 PIPE_SHADER_VERTEX); 551 } 552 553 return ctx->plain_vs->driver; 554} 555 556 557void * vg_clear_vs(struct vg_context *ctx) 558{ 559 if (!ctx->clear_vs) { 560 ctx->clear_vs = shader_create_from_text(ctx->pipe, 561 vs_clear_asm, 562 200, 563 PIPE_SHADER_VERTEX); 564 } 565 566 return ctx->clear_vs->driver; 567} 568 569void * vg_texture_vs(struct vg_context *ctx) 570{ 571 if (!ctx->texture_vs) { 572 ctx->texture_vs = shader_create_from_text(ctx->pipe, 573 vs_texture_asm, 574 200, 575 PIPE_SHADER_VERTEX); 576 } 577 578 return ctx->texture_vs->driver; 579} 580 581void vg_set_viewport(struct vg_context *ctx, VegaOrientation orientation) 582{ 583 struct pipe_viewport_state viewport; 584 struct pipe_framebuffer_state *fb = &ctx->state.g3d.fb; 585 VGfloat y_scale = (orientation == VEGA_Y0_BOTTOM) ? -2.f : 2.f; 586 587 viewport.scale[0] = fb->width / 2.f; 588 viewport.scale[1] = fb->height / y_scale; 589 viewport.scale[2] = 1.0; 590 viewport.scale[3] = 1.0; 591 viewport.translate[0] = fb->width / 2.f; 592 viewport.translate[1] = fb->height / 2.f; 593 viewport.translate[2] = 0.0; 594 viewport.translate[3] = 0.0; 595 596 cso_set_viewport(ctx->cso_context, &viewport); 597} 598