/************************************************************************** * * Copyright 2009 VMware, Inc. All Rights Reserved. * Copyright 2010 LunarG, Inc. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sub license, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial portions * of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * **************************************************************************/ #include "renderer.h" #include "vg_context.h" #include "pipe/p_context.h" #include "pipe/p_state.h" #include "util/u_inlines.h" #include "pipe/p_screen.h" #include "pipe/p_shader_tokens.h" #include "util/u_draw_quad.h" #include "util/u_simple_shaders.h" #include "util/u_memory.h" #include "util/u_sampler.h" #include "cso_cache/cso_context.h" #include "tgsi/tgsi_ureg.h" typedef enum { RENDERER_STATE_INIT, RENDERER_STATE_COPY, NUM_RENDERER_STATES } RendererState; typedef enum { RENDERER_VS_PLAIN, RENDERER_VS_COLOR, RENDERER_VS_TEXTURE, NUM_RENDERER_VS } RendererVs; typedef enum { RENDERER_FS_COLOR, RENDERER_FS_TEXTURE, NUM_RENDERER_FS } RendererFs; struct renderer { struct pipe_context *pipe; struct vg_context *owner; struct cso_context *cso; void *fs; VGfloat vertices[4][2][4]; void *cached_vs[NUM_RENDERER_VS]; void *cached_fs[NUM_RENDERER_FS]; RendererState state; /* state data */ union { struct { VGint tex_width; VGint tex_height; } copy; } u; }; /** * Return VG_TRUE if the renderer can use the resource as the asked bindings. */ static VGboolean renderer_can_support(struct renderer *renderer, struct pipe_resource *res, unsigned bindings) { struct pipe_screen *screen = renderer->pipe->screen; return screen->is_format_supported(screen, res->format, res->target, 0, bindings, 0); } /** * Create a simple vertex shader that passes through position and the given * attribute. */ static void *create_passthrough_vs(struct pipe_context *pipe, int semantic_name) { struct ureg_program *ureg; struct ureg_src src[2], constants[2]; struct ureg_dst dst[2], tmp; int i; ureg = ureg_create(TGSI_PROCESSOR_VERTEX); if (!ureg) return NULL; /* position in surface coordinates */ src[0] = ureg_DECL_vs_input(ureg, 0); dst[0] = ureg_DECL_output(ureg, TGSI_SEMANTIC_POSITION, 0); tmp = ureg_DECL_temporary(ureg); for (i = 0; i < 2; i++) constants[i] = ureg_DECL_constant(ureg, i); /* transform to clipped coordinates */ ureg_MUL(ureg, tmp, src[0], constants[0]); ureg_ADD(ureg, tmp, ureg_src(tmp), constants[1]); ureg_MOV(ureg, dst[0], ureg_src(tmp)); if (semantic_name >= 0) { src[1] = ureg_DECL_vs_input(ureg, 1); dst[1] = ureg_DECL_output(ureg, semantic_name, 0); ureg_MOV(ureg, dst[1], src[1]); } ureg_END(ureg); return ureg_create_shader_and_destroy(ureg, pipe); } /** * Set renderer vertex shader. * * This function modifies vertex_shader state. */ static void renderer_set_vs(struct renderer *r, RendererVs id) { /* create as needed */ if (!r->cached_vs[id]) { int semantic_name = -1; switch (id) { case RENDERER_VS_PLAIN: break; case RENDERER_VS_COLOR: semantic_name = TGSI_SEMANTIC_COLOR; break; case RENDERER_VS_TEXTURE: semantic_name = TGSI_SEMANTIC_GENERIC; break; default: assert(!"Unknown renderer vs id"); break; } r->cached_vs[id] = create_passthrough_vs(r->pipe, semantic_name); } cso_set_vertex_shader_handle(r->cso, r->cached_vs[id]); } /** * Set renderer fragment shader. * * This function modifies fragment_shader state. */ static void renderer_set_fs(struct renderer *r, RendererFs id) { /* create as needed */ if (!r->cached_fs[id]) { void *fs = NULL; switch (id) { case RENDERER_FS_COLOR: fs = util_make_fragment_passthrough_shader(r->pipe); break; case RENDERER_FS_TEXTURE: fs = util_make_fragment_tex_shader(r->pipe, TGSI_TEXTURE_2D, TGSI_INTERPOLATE_LINEAR); break; default: assert(!"Unknown renderer fs id"); break; } r->cached_fs[id] = fs; } cso_set_fragment_shader_handle(r->cso, r->cached_fs[id]); } /** * Set renderer target. * * This function modifies framebuffer and viewport states. */ static void renderer_set_target(struct renderer *r, struct pipe_surface *cbuf, struct pipe_surface *zsbuf, VGboolean y0_top) { struct pipe_framebuffer_state fb; memset(&fb, 0, sizeof(fb)); fb.width = cbuf->width; fb.height = cbuf->height; fb.cbufs[0] = cbuf; fb.nr_cbufs = 1; fb.zsbuf = zsbuf; cso_set_framebuffer(r->cso, &fb); vg_set_viewport(r->owner, (y0_top) ? VEGA_Y0_TOP : VEGA_Y0_BOTTOM); } /** * Set renderer blend state. Blending is disabled. * * This function modifies blend state. */ static void renderer_set_blend(struct renderer *r, VGbitfield channel_mask) { struct pipe_blend_state blend; memset(&blend, 0, sizeof(blend)); blend.rt[0].rgb_src_factor = PIPE_BLENDFACTOR_ONE; blend.rt[0].alpha_src_factor = PIPE_BLENDFACTOR_ONE; blend.rt[0].rgb_dst_factor = PIPE_BLENDFACTOR_ZERO; blend.rt[0].alpha_dst_factor = PIPE_BLENDFACTOR_ZERO; if (channel_mask & VG_RED) blend.rt[0].colormask |= PIPE_MASK_R; if (channel_mask & VG_GREEN) blend.rt[0].colormask |= PIPE_MASK_G; if (channel_mask & VG_BLUE) blend.rt[0].colormask |= PIPE_MASK_B; if (channel_mask & VG_ALPHA) blend.rt[0].colormask |= PIPE_MASK_A; cso_set_blend(r->cso, &blend); } /** * Set renderer sampler and view states. * * This function modifies samplers and fragment_sampler_views states. */ static void renderer_set_samplers(struct renderer *r, uint num_views, struct pipe_sampler_view **views) { struct pipe_sampler_state sampler; unsigned tex_filter = PIPE_TEX_FILTER_NEAREST; unsigned tex_wrap = PIPE_TEX_WRAP_CLAMP_TO_EDGE; uint i; memset(&sampler, 0, sizeof(sampler)); sampler.min_img_filter = tex_filter; sampler.mag_img_filter = tex_filter; sampler.min_mip_filter = PIPE_TEX_MIPFILTER_NONE; sampler.wrap_s = tex_wrap; sampler.wrap_t = tex_wrap; sampler.wrap_r = PIPE_TEX_WRAP_CLAMP_TO_EDGE; sampler.normalized_coords = 1; /* set samplers */ for (i = 0; i < num_views; i++) cso_single_sampler(r->cso, i, &sampler); cso_single_sampler_done(r->cso); /* set views */ cso_set_fragment_sampler_views(r->cso, num_views, views); } /** * Setup renderer quad position. */ static void renderer_quad_pos(struct renderer *r, VGfloat x0, VGfloat y0, VGfloat x1, VGfloat y1, VGboolean scissor) { VGfloat z; /* the depth test is used for scissoring */ z = (scissor) ? 0.0f : 1.0f; /* positions */ r->vertices[0][0][0] = x0; r->vertices[0][0][1] = y0; r->vertices[0][0][2] = z; r->vertices[1][0][0] = x1; r->vertices[1][0][1] = y0; r->vertices[1][0][2] = z; r->vertices[2][0][0] = x1; r->vertices[2][0][1] = y1; r->vertices[2][0][2] = z; r->vertices[3][0][0] = x0; r->vertices[3][0][1] = y1; r->vertices[3][0][2] = z; } /** * Setup renderer quad texture coordinates. */ static void renderer_quad_texcoord(struct renderer *r, VGfloat x0, VGfloat y0, VGfloat x1, VGfloat y1, VGint tex_width, VGint tex_height) { VGfloat s0, t0, s1, t1, r0, q0; VGint i; s0 = x0 / tex_width; s1 = x1 / tex_width; t0 = y0 / tex_height; t1 = y1 / tex_height; r0 = 0.0f; q0 = 1.0f; /* texcoords */ r->vertices[0][1][0] = s0; r->vertices[0][1][1] = t0; r->vertices[1][1][0] = s1; r->vertices[1][1][1] = t0; r->vertices[2][1][0] = s1; r->vertices[2][1][1] = t1; r->vertices[3][1][0] = s0; r->vertices[3][1][1] = t1; for (i = 0; i < 4; i++) { r->vertices[i][1][2] = r0; r->vertices[i][1][3] = q0; } } /** * Draw renderer quad. */ static void renderer_quad_draw(struct renderer *r) { struct pipe_resource *buf; buf = pipe_user_buffer_create(r->pipe->screen, r->vertices, sizeof(r->vertices), PIPE_BIND_VERTEX_BUFFER); if (buf) { cso_set_vertex_elements(r->cso, 2, r->owner->velems); util_draw_vertex_buffer(r->pipe, buf, 0, PIPE_PRIM_TRIANGLE_FAN, Elements(r->vertices), /* verts */ Elements(r->vertices[0])); /* attribs/vert */ pipe_resource_reference(&buf, NULL); } } /** * Prepare the renderer for copying. */ VGboolean renderer_copy_begin(struct renderer *renderer, struct pipe_surface *dst, VGboolean y0_top, struct pipe_sampler_view *src) { assert(renderer->state == RENDERER_STATE_INIT); /* sanity check */ if (!renderer_can_support(renderer, dst->texture, PIPE_BIND_RENDER_TARGET) || !renderer_can_support(renderer, src->texture, PIPE_BIND_SAMPLER_VIEW)) return VG_FALSE; cso_save_framebuffer(renderer->cso); cso_save_viewport(renderer->cso); cso_save_blend(renderer->cso); cso_save_samplers(renderer->cso); cso_save_fragment_sampler_views(renderer->cso); cso_save_fragment_shader(renderer->cso); cso_save_vertex_shader(renderer->cso); renderer_set_target(renderer, dst, NULL, y0_top); renderer_set_blend(renderer, ~0); renderer_set_samplers(renderer, 1, &src); renderer_set_fs(renderer, RENDERER_FS_TEXTURE); renderer_set_vs(renderer, RENDERER_VS_TEXTURE); /* remember the texture size */ renderer->u.copy.tex_width = src->texture->width0; renderer->u.copy.tex_height = src->texture->height0; renderer->state = RENDERER_STATE_COPY; return VG_TRUE; } /** * Draw into the destination rectangle given by (x, y, w, h). The texture is * sampled from within the rectangle given by (sx, sy, sw, sh). * * The coordinates are in surface coordinates. */ void renderer_copy(struct renderer *renderer, VGint x, VGint y, VGint w, VGint h, VGint sx, VGint sy, VGint sw, VGint sh) { assert(renderer->state == RENDERER_STATE_COPY); /* there is no depth buffer for scissoring anyway */ renderer_quad_pos(renderer, x, y, x + w, y + h, VG_FALSE); renderer_quad_texcoord(renderer, sx, sy, sx + sw, sy + sh, renderer->u.copy.tex_width, renderer->u.copy.tex_height); renderer_quad_draw(renderer); } /** * End copying and restore the states. */ void renderer_copy_end(struct renderer *renderer) { assert(renderer->state == RENDERER_STATE_COPY); cso_restore_framebuffer(renderer->cso); cso_restore_viewport(renderer->cso); cso_restore_blend(renderer->cso); cso_restore_samplers(renderer->cso); cso_restore_fragment_sampler_views(renderer->cso); cso_restore_fragment_shader(renderer->cso); cso_restore_vertex_shader(renderer->cso); renderer->state = RENDERER_STATE_INIT; } static void setup_shaders(struct renderer *ctx) { struct pipe_context *pipe = ctx->pipe; /* fragment shader */ ctx->fs = util_make_fragment_tex_shader(pipe, TGSI_TEXTURE_2D, TGSI_INTERPOLATE_LINEAR); } static struct pipe_resource * setup_vertex_data(struct renderer *ctx, float x0, float y0, float x1, float y1, float z) { ctx->vertices[0][0][0] = x0; ctx->vertices[0][0][1] = y0; ctx->vertices[0][0][2] = z; ctx->vertices[0][1][0] = 0.0f; /*s*/ ctx->vertices[0][1][1] = 0.0f; /*t*/ ctx->vertices[1][0][0] = x1; ctx->vertices[1][0][1] = y0; ctx->vertices[1][0][2] = z; ctx->vertices[1][1][0] = 1.0f; /*s*/ ctx->vertices[1][1][1] = 0.0f; /*t*/ ctx->vertices[2][0][0] = x1; ctx->vertices[2][0][1] = y1; ctx->vertices[2][0][2] = z; ctx->vertices[2][1][0] = 1.0f; ctx->vertices[2][1][1] = 1.0f; ctx->vertices[3][0][0] = x0; ctx->vertices[3][0][1] = y1; ctx->vertices[3][0][2] = z; ctx->vertices[3][1][0] = 0.0f; ctx->vertices[3][1][1] = 1.0f; return pipe_user_buffer_create( ctx->pipe->screen, ctx->vertices, sizeof(ctx->vertices), PIPE_BIND_VERTEX_BUFFER); } struct renderer * renderer_create(struct vg_context *owner) { VGint i; struct renderer *renderer = CALLOC_STRUCT(renderer); if (!renderer) return NULL; renderer->owner = owner; renderer->pipe = owner->pipe; renderer->cso = owner->cso_context; setup_shaders(renderer); /* init vertex data that doesn't change */ for (i = 0; i < 4; i++) renderer->vertices[i][0][3] = 1.0f; /* w */ renderer->state = RENDERER_STATE_INIT; return renderer; } void renderer_destroy(struct renderer *ctx) { int i; for (i = 0; i < NUM_RENDERER_VS; i++) { if (ctx->cached_vs[i]) cso_delete_vertex_shader(ctx->cso, ctx->cached_vs[i]); } for (i = 0; i < NUM_RENDERER_FS; i++) { if (ctx->cached_fs[i]) cso_delete_fragment_shader(ctx->cso, ctx->cached_fs[i]); } #if 0 if (ctx->fs) { cso_delete_fragment_shader(ctx->cso, ctx->fs); ctx->fs = NULL; } #endif FREE(ctx); } void renderer_draw_quad(struct renderer *r, VGfloat x1, VGfloat y1, VGfloat x2, VGfloat y2, VGfloat depth) { assert(r->state == RENDERER_STATE_INIT); assert(floatsEqual(depth, 0.0f)); renderer_quad_pos(r, x1, y1, x2, y2, VG_TRUE); renderer_quad_draw(r); } void renderer_draw_texture(struct renderer *r, struct pipe_resource *tex, VGfloat x1offset, VGfloat y1offset, VGfloat x2offset, VGfloat y2offset, VGfloat x1, VGfloat y1, VGfloat x2, VGfloat y2) { assert(r->state == RENDERER_STATE_INIT); assert(tex->width0 != 0); assert(tex->height0 != 0); cso_save_vertex_shader(r->cso); renderer_set_vs(r, RENDERER_VS_TEXTURE); renderer_quad_pos(r, x1, y1, x2, y2, VG_TRUE); renderer_quad_texcoord(r, x1offset, y1offset, x2offset, y2offset, tex->width0, tex->height0); renderer_quad_draw(r); cso_restore_vertex_shader(r->cso); } void renderer_copy_texture(struct renderer *ctx, struct pipe_sampler_view *src, VGfloat sx1, VGfloat sy1, VGfloat sx2, VGfloat sy2, struct pipe_resource *dst, VGfloat dx1, VGfloat dy1, VGfloat dx2, VGfloat dy2) { struct pipe_surface *surf; VGint x, y, w, h, sx, sy, sw, sh; /* get the destination surface */ surf = ctx->pipe->screen->get_tex_surface(ctx->pipe->screen, dst, 0, 0, 0, PIPE_BIND_RENDER_TARGET); if (!surf) return; assert(ctx->state == RENDERER_STATE_INIT); assert(src->texture->width0 != 0); assert(src->texture->height0 != 0); assert(dst->width0 != 0); assert(dst->height0 != 0); x = (VGint) dx1; y = (VGint) dy1; w = (VGint) (dx2 - dx1); h = (VGint) (dy2 - dy1); assert(floatsEqual(x, dx1) && floatsEqual(y, dy1) && floatsEqual(w, (dx2 - dx1)) && floatsEqual(h, (dy2 - dy1))); sx = (VGint) sx1; sy = (VGint) sy1; sw = (VGint) (sx2 - sx1); sh = (VGint) (sy2 - sy1); assert(floatsEqual(sx, sx1) && floatsEqual(sy, sy1) && floatsEqual(sw, (sx2 - sx1)) && floatsEqual(sh, (sy2 - sy1))); if (renderer_copy_begin(ctx, surf, VG_TRUE, src)) { renderer_copy(ctx, x, y, w, h, sx, sy, sw, sh); renderer_copy_end(ctx); } pipe_surface_reference(&surf, NULL); } void renderer_copy_surface(struct renderer *ctx, struct pipe_surface *src, int srcX0, int srcY0, int srcX1, int srcY1, struct pipe_surface *dst, int dstX0, int dstY0, int dstX1, int dstY1, float z, unsigned filter) { struct pipe_context *pipe = ctx->pipe; struct pipe_screen *screen = pipe->screen; struct pipe_resource *buf; struct pipe_sampler_view view_templ; struct pipe_sampler_view *view; struct pipe_resource texTemp, *tex; struct pipe_subresource subsrc, subdst; struct pipe_framebuffer_state fb; struct st_framebuffer *stfb = ctx->owner->draw_buffer; const int srcW = abs(srcX1 - srcX0); const int srcH = abs(srcY1 - srcY0); const int srcLeft = MIN2(srcX0, srcX1); const int srcTop = MIN2(srcY0, srcY1); assert(filter == PIPE_TEX_MIPFILTER_NEAREST || filter == PIPE_TEX_MIPFILTER_LINEAR); if (srcLeft != srcX0) { /* left-right flip */ int tmp = dstX0; dstX0 = dstX1; dstX1 = tmp; } if (srcTop != srcY0) { /* up-down flip */ int tmp = dstY0; dstY0 = dstY1; dstY1 = tmp; } assert(screen->is_format_supported(screen, src->format, PIPE_TEXTURE_2D, 0, PIPE_BIND_SAMPLER_VIEW, 0)); assert(screen->is_format_supported(screen, dst->format, PIPE_TEXTURE_2D, 0, PIPE_BIND_SAMPLER_VIEW, 0)); assert(screen->is_format_supported(screen, dst->format, PIPE_TEXTURE_2D, 0, PIPE_BIND_RENDER_TARGET, 0)); /* * XXX for now we're always creating a temporary texture. * Strictly speaking that's not always needed. */ /* create temp texture */ memset(&texTemp, 0, sizeof(texTemp)); texTemp.target = PIPE_TEXTURE_2D; texTemp.format = src->format; texTemp.last_level = 0; texTemp.width0 = srcW; texTemp.height0 = srcH; texTemp.depth0 = 1; texTemp.bind = PIPE_BIND_SAMPLER_VIEW; tex = screen->resource_create(screen, &texTemp); if (!tex) return; u_sampler_view_default_template(&view_templ, tex, tex->format); view = pipe->create_sampler_view(pipe, tex, &view_templ); if (!view) return; subdst.face = 0; subdst.level = 0; subsrc.face = src->face; subsrc.level = src->level; pipe->resource_copy_region(pipe, tex, subdst, 0, 0, 0, /* dest */ src->texture, subsrc, srcLeft, srcTop, src->zslice, /* src */ srcW, srcH); /* size */ /* save state (restored below) */ cso_save_blend(ctx->cso); cso_save_samplers(ctx->cso); cso_save_fragment_sampler_views(ctx->cso); cso_save_framebuffer(ctx->cso); cso_save_fragment_shader(ctx->cso); cso_save_vertex_shader(ctx->cso); cso_save_viewport(ctx->cso); /* set misc state we care about */ { struct pipe_blend_state blend; memset(&blend, 0, sizeof(blend)); blend.rt[0].rgb_src_factor = PIPE_BLENDFACTOR_ONE; blend.rt[0].alpha_src_factor = PIPE_BLENDFACTOR_ONE; blend.rt[0].rgb_dst_factor = PIPE_BLENDFACTOR_ZERO; blend.rt[0].alpha_dst_factor = PIPE_BLENDFACTOR_ZERO; blend.rt[0].colormask = PIPE_MASK_RGBA; cso_set_blend(ctx->cso, &blend); } vg_set_viewport(ctx->owner, VEGA_Y0_TOP); /* sampler */ { struct pipe_sampler_state sampler; memset(&sampler, 0, sizeof(sampler)); sampler.wrap_s = PIPE_TEX_WRAP_CLAMP_TO_EDGE; sampler.wrap_t = PIPE_TEX_WRAP_CLAMP_TO_EDGE; sampler.wrap_r = PIPE_TEX_WRAP_CLAMP_TO_EDGE; sampler.min_mip_filter = PIPE_TEX_MIPFILTER_NONE; sampler.min_img_filter = PIPE_TEX_FILTER_NEAREST; sampler.mag_img_filter = PIPE_TEX_FILTER_NEAREST; sampler.normalized_coords = 1; cso_single_sampler(ctx->cso, 0, &sampler); cso_single_sampler_done(ctx->cso); } /* texture */ cso_set_fragment_sampler_views(ctx->cso, 1, &view); /* shaders */ cso_set_fragment_shader_handle(ctx->cso, ctx->fs); cso_set_vertex_shader_handle(ctx->cso, vg_texture_vs(ctx->owner)); /* drawing dest */ if (stfb->strb->surface != dst) { memset(&fb, 0, sizeof(fb)); fb.width = dst->width; fb.height = dst->height; fb.nr_cbufs = 1; fb.cbufs[0] = dst; fb.zsbuf = stfb->dsrb->surface; cso_set_framebuffer(ctx->cso, &fb); } /* draw quad */ buf = setup_vertex_data(ctx, (float) dstX0, (float) dstY0, (float) dstX1, (float) dstY1, z); if (buf) { cso_set_vertex_elements(ctx->cso, 2, ctx->owner->velems); util_draw_vertex_buffer(ctx->pipe, buf, 0, PIPE_PRIM_TRIANGLE_FAN, 4, /* verts */ 2); /* attribs/vert */ pipe_resource_reference( &buf, NULL ); } /* restore state we changed */ cso_restore_blend(ctx->cso); cso_restore_samplers(ctx->cso); cso_restore_fragment_sampler_views(ctx->cso); cso_restore_framebuffer(ctx->cso); cso_restore_fragment_shader(ctx->cso); cso_restore_vertex_shader(ctx->cso); cso_restore_viewport(ctx->cso); pipe_resource_reference(&tex, NULL); pipe_sampler_view_reference(&view, NULL); } void renderer_texture_quad(struct renderer *r, struct pipe_resource *tex, VGfloat x1offset, VGfloat y1offset, VGfloat x2offset, VGfloat y2offset, VGfloat x1, VGfloat y1, VGfloat x2, VGfloat y2, VGfloat x3, VGfloat y3, VGfloat x4, VGfloat y4) { const VGfloat z = 0.0f; assert(r->state == RENDERER_STATE_INIT); assert(tex->width0 != 0); assert(tex->height0 != 0); cso_save_vertex_shader(r->cso); renderer_set_vs(r, RENDERER_VS_TEXTURE); /* manually set up positions */ r->vertices[0][0][0] = x1; r->vertices[0][0][1] = y1; r->vertices[0][0][2] = z; r->vertices[1][0][0] = x2; r->vertices[1][0][1] = y2; r->vertices[1][0][2] = z; r->vertices[2][0][0] = x3; r->vertices[2][0][1] = y3; r->vertices[2][0][2] = z; r->vertices[3][0][0] = x4; r->vertices[3][0][1] = y4; r->vertices[3][0][2] = z; /* texcoords */ renderer_quad_texcoord(r, x1offset, y1offset, x2offset, y2offset, tex->width0, tex->height0); renderer_quad_draw(r); cso_restore_vertex_shader(r->cso); }