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