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