Path: blob/21.2-virgl/src/gallium/drivers/softpipe/sp_setup.c
4570 views
/**************************************************************************1*2* Copyright 2007 VMware, Inc.3* All Rights Reserved.4*5* Permission is hereby granted, free of charge, to any person obtaining a6* copy of this software and associated documentation files (the7* "Software"), to deal in the Software without restriction, including8* without limitation the rights to use, copy, modify, merge, publish,9* distribute, sub license, and/or sell copies of the Software, and to10* permit persons to whom the Software is furnished to do so, subject to11* the following conditions:12*13* The above copyright notice and this permission notice (including the14* next paragraph) shall be included in all copies or substantial portions15* of the Software.16*17* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS18* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF19* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.20* IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR21* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,22* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE23* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.24*25**************************************************************************/2627/**28* \brief Primitive rasterization/rendering (points, lines, triangles)29*30* \author Keith Whitwell <[email protected]>31* \author Brian Paul32*/3334#include "sp_context.h"35#include "sp_screen.h"36#include "sp_quad.h"37#include "sp_quad_pipe.h"38#include "sp_setup.h"39#include "sp_state.h"40#include "draw/draw_context.h"41#include "pipe/p_shader_tokens.h"42#include "util/u_math.h"43#include "util/u_memory.h"444546#define DEBUG_VERTS 047#define DEBUG_FRAGS 0484950/**51* Triangle edge info52*/53struct edge {54float dx; /**< X(v1) - X(v0), used only during setup */55float dy; /**< Y(v1) - Y(v0), used only during setup */56float dxdy; /**< dx/dy */57float sx, sy; /**< first sample point coord */58int lines; /**< number of lines on this edge */59};606162/**63* Max number of quads (2x2 pixel blocks) to process per batch.64* This can't be arbitrarily increased since we depend on some 32-bit65* bitmasks (two bits per quad).66*/67#define MAX_QUADS 16686970/**71* Triangle setup info.72* Also used for line drawing (taking some liberties).73*/74struct setup_context {75struct softpipe_context *softpipe;7677/* Vertices are just an array of floats making up each attribute in78* turn. Currently fixed at 4 floats, but should change in time.79* Codegen will help cope with this.80*/81const float (*vmax)[4];82const float (*vmid)[4];83const float (*vmin)[4];84const float (*vprovoke)[4];8586struct edge ebot;87struct edge etop;88struct edge emaj;8990float oneoverarea;91int facing;9293float pixel_offset;94unsigned max_layer;9596struct quad_header quad[MAX_QUADS];97struct quad_header *quad_ptrs[MAX_QUADS];98unsigned count;99100struct tgsi_interp_coef coef[PIPE_MAX_SHADER_INPUTS];101struct tgsi_interp_coef posCoef; /* For Z, W */102103struct {104int left[2]; /**< [0] = row0, [1] = row1 */105int right[2];106int y;107} span;108109#if DEBUG_FRAGS110uint numFragsEmitted; /**< per primitive */111uint numFragsWritten; /**< per primitive */112#endif113114unsigned cull_face; /* which faces cull */115unsigned nr_vertex_attrs;116};117118119120121122123124/**125* Clip setup->quad against the scissor/surface bounds.126*/127static inline void128quad_clip(struct setup_context *setup, struct quad_header *quad)129{130unsigned viewport_index = quad[0].input.viewport_index;131const struct pipe_scissor_state *cliprect = &setup->softpipe->cliprect[viewport_index];132const int minx = (int) cliprect->minx;133const int maxx = (int) cliprect->maxx;134const int miny = (int) cliprect->miny;135const int maxy = (int) cliprect->maxy;136137if (quad->input.x0 >= maxx ||138quad->input.y0 >= maxy ||139quad->input.x0 + 1 < minx ||140quad->input.y0 + 1 < miny) {141/* totally clipped */142quad->inout.mask = 0x0;143return;144}145if (quad->input.x0 < minx)146quad->inout.mask &= (MASK_BOTTOM_RIGHT | MASK_TOP_RIGHT);147if (quad->input.y0 < miny)148quad->inout.mask &= (MASK_BOTTOM_LEFT | MASK_BOTTOM_RIGHT);149if (quad->input.x0 == maxx - 1)150quad->inout.mask &= (MASK_BOTTOM_LEFT | MASK_TOP_LEFT);151if (quad->input.y0 == maxy - 1)152quad->inout.mask &= (MASK_TOP_LEFT | MASK_TOP_RIGHT);153}154155156/**157* Emit a quad (pass to next stage) with clipping.158*/159static inline void160clip_emit_quad(struct setup_context *setup, struct quad_header *quad)161{162quad_clip(setup, quad);163164if (quad->inout.mask) {165struct softpipe_context *sp = setup->softpipe;166167#if DEBUG_FRAGS168setup->numFragsEmitted += util_bitcount(quad->inout.mask);169#endif170171sp->quad.first->run( sp->quad.first, &quad, 1 );172}173}174175176177/**178* Given an X or Y coordinate, return the block/quad coordinate that it179* belongs to.180*/181static inline int182block(int x)183{184return x & ~(2-1);185}186187188static inline int189block_x(int x)190{191return x & ~(16-1);192}193194195/**196* Render a horizontal span of quads197*/198static void199flush_spans(struct setup_context *setup)200{201const int step = MAX_QUADS;202const int xleft0 = setup->span.left[0];203const int xleft1 = setup->span.left[1];204const int xright0 = setup->span.right[0];205const int xright1 = setup->span.right[1];206struct quad_stage *pipe = setup->softpipe->quad.first;207208const int minleft = block_x(MIN2(xleft0, xleft1));209const int maxright = MAX2(xright0, xright1);210int x;211212/* process quads in horizontal chunks of 16 */213for (x = minleft; x < maxright; x += step) {214unsigned skip_left0 = CLAMP(xleft0 - x, 0, step);215unsigned skip_left1 = CLAMP(xleft1 - x, 0, step);216unsigned skip_right0 = CLAMP(x + step - xright0, 0, step);217unsigned skip_right1 = CLAMP(x + step - xright1, 0, step);218unsigned lx = x;219unsigned q = 0;220221unsigned skipmask_left0 = (1U << skip_left0) - 1U;222unsigned skipmask_left1 = (1U << skip_left1) - 1U;223224/* These calculations fail when step == 32 and skip_right == 0.225*/226unsigned skipmask_right0 = ~0U << (unsigned)(step - skip_right0);227unsigned skipmask_right1 = ~0U << (unsigned)(step - skip_right1);228229unsigned mask0 = ~skipmask_left0 & ~skipmask_right0;230unsigned mask1 = ~skipmask_left1 & ~skipmask_right1;231232if (mask0 | mask1) {233do {234unsigned quadmask = (mask0 & 3) | ((mask1 & 3) << 2);235if (quadmask) {236setup->quad[q].input.x0 = lx;237setup->quad[q].input.y0 = setup->span.y;238setup->quad[q].input.facing = setup->facing;239setup->quad[q].inout.mask = quadmask;240setup->quad_ptrs[q] = &setup->quad[q];241q++;242#if DEBUG_FRAGS243setup->numFragsEmitted += util_bitcount(quadmask);244#endif245}246mask0 >>= 2;247mask1 >>= 2;248lx += 2;249} while (mask0 | mask1);250251pipe->run( pipe, setup->quad_ptrs, q );252}253}254255256setup->span.y = 0;257setup->span.right[0] = 0;258setup->span.right[1] = 0;259setup->span.left[0] = 1000000; /* greater than right[0] */260setup->span.left[1] = 1000000; /* greater than right[1] */261}262263264#if DEBUG_VERTS265static void266print_vertex(const struct setup_context *setup,267const float (*v)[4])268{269int i;270debug_printf(" Vertex: (%p)\n", (void *) v);271for (i = 0; i < setup->nr_vertex_attrs; i++) {272debug_printf(" %d: %f %f %f %f\n", i,273v[i][0], v[i][1], v[i][2], v[i][3]);274if (util_is_inf_or_nan(v[i][0])) {275debug_printf(" NaN!\n");276}277}278}279#endif280281282/**283* Sort the vertices from top to bottom order, setting up the triangle284* edge fields (ebot, emaj, etop).285* \return FALSE if coords are inf/nan (cull the tri), TRUE otherwise286*/287static boolean288setup_sort_vertices(struct setup_context *setup,289float det,290const float (*v0)[4],291const float (*v1)[4],292const float (*v2)[4])293{294if (setup->softpipe->rasterizer->flatshade_first)295setup->vprovoke = v0;296else297setup->vprovoke = v2;298299/* determine bottom to top order of vertices */300{301float y0 = v0[0][1];302float y1 = v1[0][1];303float y2 = v2[0][1];304if (y0 <= y1) {305if (y1 <= y2) {306/* y0<=y1<=y2 */307setup->vmin = v0;308setup->vmid = v1;309setup->vmax = v2;310}311else if (y2 <= y0) {312/* y2<=y0<=y1 */313setup->vmin = v2;314setup->vmid = v0;315setup->vmax = v1;316}317else {318/* y0<=y2<=y1 */319setup->vmin = v0;320setup->vmid = v2;321setup->vmax = v1;322}323}324else {325if (y0 <= y2) {326/* y1<=y0<=y2 */327setup->vmin = v1;328setup->vmid = v0;329setup->vmax = v2;330}331else if (y2 <= y1) {332/* y2<=y1<=y0 */333setup->vmin = v2;334setup->vmid = v1;335setup->vmax = v0;336}337else {338/* y1<=y2<=y0 */339setup->vmin = v1;340setup->vmid = v2;341setup->vmax = v0;342}343}344}345346setup->ebot.dx = setup->vmid[0][0] - setup->vmin[0][0];347setup->ebot.dy = setup->vmid[0][1] - setup->vmin[0][1];348setup->emaj.dx = setup->vmax[0][0] - setup->vmin[0][0];349setup->emaj.dy = setup->vmax[0][1] - setup->vmin[0][1];350setup->etop.dx = setup->vmax[0][0] - setup->vmid[0][0];351setup->etop.dy = setup->vmax[0][1] - setup->vmid[0][1];352353/*354* Compute triangle's area. Use 1/area to compute partial355* derivatives of attributes later.356*357* The area will be the same as prim->det, but the sign may be358* different depending on how the vertices get sorted above.359*360* To determine whether the primitive is front or back facing we361* use the prim->det value because its sign is correct.362*/363{364const float area = (setup->emaj.dx * setup->ebot.dy -365setup->ebot.dx * setup->emaj.dy);366367setup->oneoverarea = 1.0f / area;368369/*370debug_printf("%s one-over-area %f area %f det %f\n",371__FUNCTION__, setup->oneoverarea, area, det );372*/373if (util_is_inf_or_nan(setup->oneoverarea))374return FALSE;375}376377/* We need to know if this is a front or back-facing triangle for:378* - the GLSL gl_FrontFacing fragment attribute (bool)379* - two-sided stencil test380* 0 = front-facing, 1 = back-facing381*/382setup->facing =383((det < 0.0) ^384(setup->softpipe->rasterizer->front_ccw));385386{387unsigned face = setup->facing == 0 ? PIPE_FACE_FRONT : PIPE_FACE_BACK;388389if (face & setup->cull_face)390return FALSE;391}392393return TRUE;394}395396397/* Apply cylindrical wrapping to v0, v1, v2 coordinates, if enabled.398* Input coordinates must be in [0, 1] range, otherwise results are undefined.399* Some combinations of coordinates produce invalid results,400* but this behaviour is acceptable.401*/402static void403tri_apply_cylindrical_wrap(float v0,404float v1,405float v2,406uint cylindrical_wrap,407float output[3])408{409if (cylindrical_wrap) {410float delta;411412delta = v1 - v0;413if (delta > 0.5f) {414v0 += 1.0f;415}416else if (delta < -0.5f) {417v1 += 1.0f;418}419420delta = v2 - v1;421if (delta > 0.5f) {422v1 += 1.0f;423}424else if (delta < -0.5f) {425v2 += 1.0f;426}427428delta = v0 - v2;429if (delta > 0.5f) {430v2 += 1.0f;431}432else if (delta < -0.5f) {433v0 += 1.0f;434}435}436437output[0] = v0;438output[1] = v1;439output[2] = v2;440}441442443/**444* Compute a0 for a constant-valued coefficient (GL_FLAT shading).445* The value value comes from vertex[slot][i].446* The result will be put into setup->coef[slot].a0[i].447* \param slot which attribute slot448* \param i which component of the slot (0..3)449*/450static void451const_coeff(struct setup_context *setup,452struct tgsi_interp_coef *coef,453uint vertSlot, uint i)454{455assert(i <= 3);456457coef->dadx[i] = 0;458coef->dady[i] = 0;459460/* need provoking vertex info!461*/462coef->a0[i] = setup->vprovoke[vertSlot][i];463}464465466/**467* Compute a0, dadx and dady for a linearly interpolated coefficient,468* for a triangle.469* v[0], v[1] and v[2] are vmin, vmid and vmax, respectively.470*/471static void472tri_linear_coeff(struct setup_context *setup,473struct tgsi_interp_coef *coef,474uint i,475const float v[3])476{477float botda = v[1] - v[0];478float majda = v[2] - v[0];479float a = setup->ebot.dy * majda - botda * setup->emaj.dy;480float b = setup->emaj.dx * botda - majda * setup->ebot.dx;481float dadx = a * setup->oneoverarea;482float dady = b * setup->oneoverarea;483484assert(i <= 3);485486coef->dadx[i] = dadx;487coef->dady[i] = dady;488489/* calculate a0 as the value which would be sampled for the490* fragment at (0,0), taking into account that we want to sample at491* pixel centers, in other words (pixel_offset, pixel_offset).492*493* this is neat but unfortunately not a good way to do things for494* triangles with very large values of dadx or dady as it will495* result in the subtraction and re-addition from a0 of a very496* large number, which means we'll end up loosing a lot of the497* fractional bits and precision from a0. the way to fix this is498* to define a0 as the sample at a pixel center somewhere near vmin499* instead - i'll switch to this later.500*/501coef->a0[i] = (v[0] -502(dadx * (setup->vmin[0][0] - setup->pixel_offset) +503dady * (setup->vmin[0][1] - setup->pixel_offset)));504}505506507/**508* Compute a0, dadx and dady for a perspective-corrected interpolant,509* for a triangle.510* We basically multiply the vertex value by 1/w before computing511* the plane coefficients (a0, dadx, dady).512* Later, when we compute the value at a particular fragment position we'll513* divide the interpolated value by the interpolated W at that fragment.514* v[0], v[1] and v[2] are vmin, vmid and vmax, respectively.515*/516static void517tri_persp_coeff(struct setup_context *setup,518struct tgsi_interp_coef *coef,519uint i,520const float v[3])521{522/* premultiply by 1/w (v[0][3] is always W):523*/524float mina = v[0] * setup->vmin[0][3];525float mida = v[1] * setup->vmid[0][3];526float maxa = v[2] * setup->vmax[0][3];527float botda = mida - mina;528float majda = maxa - mina;529float a = setup->ebot.dy * majda - botda * setup->emaj.dy;530float b = setup->emaj.dx * botda - majda * setup->ebot.dx;531float dadx = a * setup->oneoverarea;532float dady = b * setup->oneoverarea;533534assert(i <= 3);535536coef->dadx[i] = dadx;537coef->dady[i] = dady;538coef->a0[i] = (mina -539(dadx * (setup->vmin[0][0] - setup->pixel_offset) +540dady * (setup->vmin[0][1] - setup->pixel_offset)));541}542543544/**545* Special coefficient setup for gl_FragCoord.546* X and Y are trivial, though Y may have to be inverted for OpenGL.547* Z and W are copied from posCoef which should have already been computed.548* We could do a bit less work if we'd examine gl_FragCoord's swizzle mask.549*/550static void551setup_fragcoord_coeff(struct setup_context *setup, uint slot)552{553const struct tgsi_shader_info *fsInfo = &setup->softpipe->fs_variant->info;554boolean origin_lower_left =555fsInfo->properties[TGSI_PROPERTY_FS_COORD_ORIGIN];556boolean pixel_center_integer =557fsInfo->properties[TGSI_PROPERTY_FS_COORD_PIXEL_CENTER];558559/*X*/560setup->coef[slot].a0[0] = pixel_center_integer ? 0.0f : 0.5f;561setup->coef[slot].dadx[0] = 1.0f;562setup->coef[slot].dady[0] = 0.0f;563/*Y*/564setup->coef[slot].a0[1] =565(origin_lower_left ? setup->softpipe->framebuffer.height-1 : 0)566+ (pixel_center_integer ? 0.0f : 0.5f);567setup->coef[slot].dadx[1] = 0.0f;568setup->coef[slot].dady[1] = origin_lower_left ? -1.0f : 1.0f;569/*Z*/570setup->coef[slot].a0[2] = setup->posCoef.a0[2];571setup->coef[slot].dadx[2] = setup->posCoef.dadx[2];572setup->coef[slot].dady[2] = setup->posCoef.dady[2];573/*W*/574setup->coef[slot].a0[3] = setup->posCoef.a0[3];575setup->coef[slot].dadx[3] = setup->posCoef.dadx[3];576setup->coef[slot].dady[3] = setup->posCoef.dady[3];577}578579580581/**582* Compute the setup->coef[] array dadx, dady, a0 values.583* Must be called after setup->vmin,vmid,vmax,vprovoke are initialized.584*/585static void586setup_tri_coefficients(struct setup_context *setup)587{588struct softpipe_context *softpipe = setup->softpipe;589const struct tgsi_shader_info *fsInfo = &setup->softpipe->fs_variant->info;590const struct sp_setup_info *sinfo = &softpipe->setup_info;591uint fragSlot;592float v[3];593594assert(sinfo->valid);595596/* z and w are done by linear interpolation:597*/598v[0] = setup->vmin[0][2];599v[1] = setup->vmid[0][2];600v[2] = setup->vmax[0][2];601tri_linear_coeff(setup, &setup->posCoef, 2, v);602603v[0] = setup->vmin[0][3];604v[1] = setup->vmid[0][3];605v[2] = setup->vmax[0][3];606tri_linear_coeff(setup, &setup->posCoef, 3, v);607608/* setup interpolation for all the remaining attributes:609*/610for (fragSlot = 0; fragSlot < fsInfo->num_inputs; fragSlot++) {611const uint vertSlot = sinfo->attrib[fragSlot].src_index;612uint j;613614switch (sinfo->attrib[fragSlot].interp) {615case SP_INTERP_CONSTANT:616for (j = 0; j < TGSI_NUM_CHANNELS; j++) {617const_coeff(setup, &setup->coef[fragSlot], vertSlot, j);618}619break;620case SP_INTERP_LINEAR:621for (j = 0; j < TGSI_NUM_CHANNELS; j++) {622tri_apply_cylindrical_wrap(setup->vmin[vertSlot][j],623setup->vmid[vertSlot][j],624setup->vmax[vertSlot][j],625fsInfo->input_cylindrical_wrap[fragSlot] & (1 << j),626v);627tri_linear_coeff(setup, &setup->coef[fragSlot], j, v);628}629break;630case SP_INTERP_PERSPECTIVE:631for (j = 0; j < TGSI_NUM_CHANNELS; j++) {632tri_apply_cylindrical_wrap(setup->vmin[vertSlot][j],633setup->vmid[vertSlot][j],634setup->vmax[vertSlot][j],635fsInfo->input_cylindrical_wrap[fragSlot] & (1 << j),636v);637tri_persp_coeff(setup, &setup->coef[fragSlot], j, v);638}639break;640case SP_INTERP_POS:641setup_fragcoord_coeff(setup, fragSlot);642break;643default:644assert(0);645}646647if (fsInfo->input_semantic_name[fragSlot] == TGSI_SEMANTIC_FACE) {648/* convert 0 to 1.0 and 1 to -1.0 */649setup->coef[fragSlot].a0[0] = setup->facing * -2.0f + 1.0f;650setup->coef[fragSlot].dadx[0] = 0.0;651setup->coef[fragSlot].dady[0] = 0.0;652}653654if (0) {655for (j = 0; j < TGSI_NUM_CHANNELS; j++) {656debug_printf("attr[%d].%c: a0:%f dx:%f dy:%f\n",657fragSlot, "xyzw"[j],658setup->coef[fragSlot].a0[j],659setup->coef[fragSlot].dadx[j],660setup->coef[fragSlot].dady[j]);661}662}663}664}665666667static void668setup_tri_edges(struct setup_context *setup)669{670float vmin_x = setup->vmin[0][0] + setup->pixel_offset;671float vmid_x = setup->vmid[0][0] + setup->pixel_offset;672673float vmin_y = setup->vmin[0][1] - setup->pixel_offset;674float vmid_y = setup->vmid[0][1] - setup->pixel_offset;675float vmax_y = setup->vmax[0][1] - setup->pixel_offset;676677setup->emaj.sy = ceilf(vmin_y);678setup->emaj.lines = (int) ceilf(vmax_y - setup->emaj.sy);679setup->emaj.dxdy = setup->emaj.dy ? setup->emaj.dx / setup->emaj.dy : .0f;680setup->emaj.sx = vmin_x + (setup->emaj.sy - vmin_y) * setup->emaj.dxdy;681682setup->etop.sy = ceilf(vmid_y);683setup->etop.lines = (int) ceilf(vmax_y - setup->etop.sy);684setup->etop.dxdy = setup->etop.dy ? setup->etop.dx / setup->etop.dy : .0f;685setup->etop.sx = vmid_x + (setup->etop.sy - vmid_y) * setup->etop.dxdy;686687setup->ebot.sy = ceilf(vmin_y);688setup->ebot.lines = (int) ceilf(vmid_y - setup->ebot.sy);689setup->ebot.dxdy = setup->ebot.dy ? setup->ebot.dx / setup->ebot.dy : .0f;690setup->ebot.sx = vmin_x + (setup->ebot.sy - vmin_y) * setup->ebot.dxdy;691}692693694/**695* Render the upper or lower half of a triangle.696* Scissoring/cliprect is applied here too.697*/698static void699subtriangle(struct setup_context *setup,700struct edge *eleft,701struct edge *eright,702int lines,703unsigned viewport_index)704{705const struct pipe_scissor_state *cliprect = &setup->softpipe->cliprect[viewport_index];706const int minx = (int) cliprect->minx;707const int maxx = (int) cliprect->maxx;708const int miny = (int) cliprect->miny;709const int maxy = (int) cliprect->maxy;710int y, start_y, finish_y;711int sy = (int)eleft->sy;712713assert((int)eleft->sy == (int) eright->sy);714assert(lines >= 0);715716/* clip top/bottom */717start_y = sy;718if (start_y < miny)719start_y = miny;720721finish_y = sy + lines;722if (finish_y > maxy)723finish_y = maxy;724725start_y -= sy;726finish_y -= sy;727728/*729debug_printf("%s %d %d\n", __FUNCTION__, start_y, finish_y);730*/731732for (y = start_y; y < finish_y; y++) {733734/* avoid accumulating adds as floats don't have the precision to735* accurately iterate large triangle edges that way. luckily we736* can just multiply these days.737*738* this is all drowned out by the attribute interpolation anyway.739*/740int left = (int)(eleft->sx + y * eleft->dxdy);741int right = (int)(eright->sx + y * eright->dxdy);742743/* clip left/right */744if (left < minx)745left = minx;746if (right > maxx)747right = maxx;748749if (left < right) {750int _y = sy + y;751if (block(_y) != setup->span.y) {752flush_spans(setup);753setup->span.y = block(_y);754}755756setup->span.left[_y&1] = left;757setup->span.right[_y&1] = right;758}759}760761762/* save the values so that emaj can be restarted:763*/764eleft->sx += lines * eleft->dxdy;765eright->sx += lines * eright->dxdy;766eleft->sy += lines;767eright->sy += lines;768}769770771/**772* Recalculate prim's determinant. This is needed as we don't have773* get this information through the vbuf_render interface & we must774* calculate it here.775*/776static float777calc_det(const float (*v0)[4],778const float (*v1)[4],779const float (*v2)[4])780{781/* edge vectors e = v0 - v2, f = v1 - v2 */782const float ex = v0[0][0] - v2[0][0];783const float ey = v0[0][1] - v2[0][1];784const float fx = v1[0][0] - v2[0][0];785const float fy = v1[0][1] - v2[0][1];786787/* det = cross(e,f).z */788return ex * fy - ey * fx;789}790791792/**793* Do setup for triangle rasterization, then render the triangle.794*/795void796sp_setup_tri(struct setup_context *setup,797const float (*v0)[4],798const float (*v1)[4],799const float (*v2)[4])800{801float det;802uint layer = 0;803unsigned viewport_index = 0;804#if DEBUG_VERTS805debug_printf("Setup triangle:\n");806print_vertex(setup, v0);807print_vertex(setup, v1);808print_vertex(setup, v2);809#endif810811if (unlikely(sp_debug & SP_DBG_NO_RAST) ||812setup->softpipe->rasterizer->rasterizer_discard)813return;814815det = calc_det(v0, v1, v2);816/*817debug_printf("%s\n", __FUNCTION__ );818*/819820#if DEBUG_FRAGS821setup->numFragsEmitted = 0;822setup->numFragsWritten = 0;823#endif824825if (!setup_sort_vertices( setup, det, v0, v1, v2 ))826return;827828setup_tri_coefficients( setup );829setup_tri_edges( setup );830831assert(setup->softpipe->reduced_prim == PIPE_PRIM_TRIANGLES);832833setup->span.y = 0;834setup->span.right[0] = 0;835setup->span.right[1] = 0;836/* setup->span.z_mode = tri_z_mode( setup->ctx ); */837if (setup->softpipe->layer_slot > 0) {838layer = *(unsigned *)setup->vprovoke[setup->softpipe->layer_slot];839layer = MIN2(layer, setup->max_layer);840}841setup->quad[0].input.layer = layer;842843if (setup->softpipe->viewport_index_slot > 0) {844unsigned *udata = (unsigned*)v0[setup->softpipe->viewport_index_slot];845viewport_index = sp_clamp_viewport_idx(*udata);846}847setup->quad[0].input.viewport_index = viewport_index;848849/* init_constant_attribs( setup ); */850851if (setup->oneoverarea < 0.0) {852/* emaj on left:853*/854subtriangle(setup, &setup->emaj, &setup->ebot, setup->ebot.lines, viewport_index);855subtriangle(setup, &setup->emaj, &setup->etop, setup->etop.lines, viewport_index);856}857else {858/* emaj on right:859*/860subtriangle(setup, &setup->ebot, &setup->emaj, setup->ebot.lines, viewport_index);861subtriangle(setup, &setup->etop, &setup->emaj, setup->etop.lines, viewport_index);862}863864flush_spans( setup );865866if (setup->softpipe->active_statistics_queries) {867setup->softpipe->pipeline_statistics.c_primitives++;868}869870#if DEBUG_FRAGS871printf("Tri: %u frags emitted, %u written\n",872setup->numFragsEmitted,873setup->numFragsWritten);874#endif875}876877878/* Apply cylindrical wrapping to v0, v1 coordinates, if enabled.879* Input coordinates must be in [0, 1] range, otherwise results are undefined.880*/881static void882line_apply_cylindrical_wrap(float v0,883float v1,884uint cylindrical_wrap,885float output[2])886{887if (cylindrical_wrap) {888float delta;889890delta = v1 - v0;891if (delta > 0.5f) {892v0 += 1.0f;893}894else if (delta < -0.5f) {895v1 += 1.0f;896}897}898899output[0] = v0;900output[1] = v1;901}902903904/**905* Compute a0, dadx and dady for a linearly interpolated coefficient,906* for a line.907* v[0] and v[1] are vmin and vmax, respectively.908*/909static void910line_linear_coeff(const struct setup_context *setup,911struct tgsi_interp_coef *coef,912uint i,913const float v[2])914{915const float da = v[1] - v[0];916const float dadx = da * setup->emaj.dx * setup->oneoverarea;917const float dady = da * setup->emaj.dy * setup->oneoverarea;918coef->dadx[i] = dadx;919coef->dady[i] = dady;920coef->a0[i] = (v[0] -921(dadx * (setup->vmin[0][0] - setup->pixel_offset) +922dady * (setup->vmin[0][1] - setup->pixel_offset)));923}924925926/**927* Compute a0, dadx and dady for a perspective-corrected interpolant,928* for a line.929* v[0] and v[1] are vmin and vmax, respectively.930*/931static void932line_persp_coeff(const struct setup_context *setup,933struct tgsi_interp_coef *coef,934uint i,935const float v[2])936{937const float a0 = v[0] * setup->vmin[0][3];938const float a1 = v[1] * setup->vmax[0][3];939const float da = a1 - a0;940const float dadx = da * setup->emaj.dx * setup->oneoverarea;941const float dady = da * setup->emaj.dy * setup->oneoverarea;942coef->dadx[i] = dadx;943coef->dady[i] = dady;944coef->a0[i] = (a0 -945(dadx * (setup->vmin[0][0] - setup->pixel_offset) +946dady * (setup->vmin[0][1] - setup->pixel_offset)));947}948949950/**951* Compute the setup->coef[] array dadx, dady, a0 values.952* Must be called after setup->vmin,vmax are initialized.953*/954static boolean955setup_line_coefficients(struct setup_context *setup,956const float (*v0)[4],957const float (*v1)[4])958{959struct softpipe_context *softpipe = setup->softpipe;960const struct tgsi_shader_info *fsInfo = &setup->softpipe->fs_variant->info;961const struct sp_setup_info *sinfo = &softpipe->setup_info;962uint fragSlot;963float area;964float v[2];965966assert(sinfo->valid);967968/* use setup->vmin, vmax to point to vertices */969if (softpipe->rasterizer->flatshade_first)970setup->vprovoke = v0;971else972setup->vprovoke = v1;973setup->vmin = v0;974setup->vmax = v1;975976setup->emaj.dx = setup->vmax[0][0] - setup->vmin[0][0];977setup->emaj.dy = setup->vmax[0][1] - setup->vmin[0][1];978979/* NOTE: this is not really area but something proportional to it */980area = setup->emaj.dx * setup->emaj.dx + setup->emaj.dy * setup->emaj.dy;981if (area == 0.0f || util_is_inf_or_nan(area))982return FALSE;983setup->oneoverarea = 1.0f / area;984985/* z and w are done by linear interpolation:986*/987v[0] = setup->vmin[0][2];988v[1] = setup->vmax[0][2];989line_linear_coeff(setup, &setup->posCoef, 2, v);990991v[0] = setup->vmin[0][3];992v[1] = setup->vmax[0][3];993line_linear_coeff(setup, &setup->posCoef, 3, v);994995/* setup interpolation for all the remaining attributes:996*/997for (fragSlot = 0; fragSlot < fsInfo->num_inputs; fragSlot++) {998const uint vertSlot = sinfo->attrib[fragSlot].src_index;999uint j;10001001switch (sinfo->attrib[fragSlot].interp) {1002case SP_INTERP_CONSTANT:1003for (j = 0; j < TGSI_NUM_CHANNELS; j++)1004const_coeff(setup, &setup->coef[fragSlot], vertSlot, j);1005break;1006case SP_INTERP_LINEAR:1007for (j = 0; j < TGSI_NUM_CHANNELS; j++) {1008line_apply_cylindrical_wrap(setup->vmin[vertSlot][j],1009setup->vmax[vertSlot][j],1010fsInfo->input_cylindrical_wrap[fragSlot] & (1 << j),1011v);1012line_linear_coeff(setup, &setup->coef[fragSlot], j, v);1013}1014break;1015case SP_INTERP_PERSPECTIVE:1016for (j = 0; j < TGSI_NUM_CHANNELS; j++) {1017line_apply_cylindrical_wrap(setup->vmin[vertSlot][j],1018setup->vmax[vertSlot][j],1019fsInfo->input_cylindrical_wrap[fragSlot] & (1 << j),1020v);1021line_persp_coeff(setup, &setup->coef[fragSlot], j, v);1022}1023break;1024case SP_INTERP_POS:1025setup_fragcoord_coeff(setup, fragSlot);1026break;1027default:1028assert(0);1029}10301031if (fsInfo->input_semantic_name[fragSlot] == TGSI_SEMANTIC_FACE) {1032/* convert 0 to 1.0 and 1 to -1.0 */1033setup->coef[fragSlot].a0[0] = setup->facing * -2.0f + 1.0f;1034setup->coef[fragSlot].dadx[0] = 0.0;1035setup->coef[fragSlot].dady[0] = 0.0;1036}1037}1038return TRUE;1039}104010411042/**1043* Plot a pixel in a line segment.1044*/1045static inline void1046plot(struct setup_context *setup, int x, int y)1047{1048const int iy = y & 1;1049const int ix = x & 1;1050const int quadX = x - ix;1051const int quadY = y - iy;1052const int mask = (1 << ix) << (2 * iy);10531054if (quadX != setup->quad[0].input.x0 ||1055quadY != setup->quad[0].input.y0)1056{1057/* flush prev quad, start new quad */10581059if (setup->quad[0].input.x0 != -1)1060clip_emit_quad(setup, &setup->quad[0]);10611062setup->quad[0].input.x0 = quadX;1063setup->quad[0].input.y0 = quadY;1064setup->quad[0].inout.mask = 0x0;1065}10661067setup->quad[0].inout.mask |= mask;1068}106910701071/**1072* Do setup for line rasterization, then render the line.1073* Single-pixel width, no stipple, etc. We rely on the 'draw' module1074* to handle stippling and wide lines.1075*/1076void1077sp_setup_line(struct setup_context *setup,1078const float (*v0)[4],1079const float (*v1)[4])1080{1081int x0 = (int) v0[0][0];1082int x1 = (int) v1[0][0];1083int y0 = (int) v0[0][1];1084int y1 = (int) v1[0][1];1085int dx = x1 - x0;1086int dy = y1 - y0;1087int xstep, ystep;1088uint layer = 0;1089unsigned viewport_index = 0;10901091#if DEBUG_VERTS1092debug_printf("Setup line:\n");1093print_vertex(setup, v0);1094print_vertex(setup, v1);1095#endif10961097if (unlikely(sp_debug & SP_DBG_NO_RAST) ||1098setup->softpipe->rasterizer->rasterizer_discard)1099return;11001101if (dx == 0 && dy == 0)1102return;11031104if (!setup_line_coefficients(setup, v0, v1))1105return;11061107assert(v0[0][0] < 1.0e9);1108assert(v0[0][1] < 1.0e9);1109assert(v1[0][0] < 1.0e9);1110assert(v1[0][1] < 1.0e9);11111112if (dx < 0) {1113dx = -dx; /* make positive */1114xstep = -1;1115}1116else {1117xstep = 1;1118}11191120if (dy < 0) {1121dy = -dy; /* make positive */1122ystep = -1;1123}1124else {1125ystep = 1;1126}11271128assert(dx >= 0);1129assert(dy >= 0);1130assert(setup->softpipe->reduced_prim == PIPE_PRIM_LINES);11311132setup->quad[0].input.x0 = setup->quad[0].input.y0 = -1;1133setup->quad[0].inout.mask = 0x0;1134if (setup->softpipe->layer_slot > 0) {1135layer = *(unsigned *)setup->vprovoke[setup->softpipe->layer_slot];1136layer = MIN2(layer, setup->max_layer);1137}1138setup->quad[0].input.layer = layer;11391140if (setup->softpipe->viewport_index_slot > 0) {1141unsigned *udata = (unsigned*)setup->vprovoke[setup->softpipe->viewport_index_slot];1142viewport_index = sp_clamp_viewport_idx(*udata);1143}1144setup->quad[0].input.viewport_index = viewport_index;11451146/* XXX temporary: set coverage to 1.0 so the line appears1147* if AA mode happens to be enabled.1148*/1149setup->quad[0].input.coverage[0] =1150setup->quad[0].input.coverage[1] =1151setup->quad[0].input.coverage[2] =1152setup->quad[0].input.coverage[3] = 1.0;11531154if (dx > dy) {1155/*** X-major line ***/1156int i;1157const int errorInc = dy + dy;1158int error = errorInc - dx;1159const int errorDec = error - dx;11601161for (i = 0; i < dx; i++) {1162plot(setup, x0, y0);11631164x0 += xstep;1165if (error < 0) {1166error += errorInc;1167}1168else {1169error += errorDec;1170y0 += ystep;1171}1172}1173}1174else {1175/*** Y-major line ***/1176int i;1177const int errorInc = dx + dx;1178int error = errorInc - dy;1179const int errorDec = error - dy;11801181for (i = 0; i < dy; i++) {1182plot(setup, x0, y0);11831184y0 += ystep;1185if (error < 0) {1186error += errorInc;1187}1188else {1189error += errorDec;1190x0 += xstep;1191}1192}1193}11941195/* draw final quad */1196if (setup->quad[0].inout.mask) {1197clip_emit_quad(setup, &setup->quad[0]);1198}1199}120012011202static void1203point_persp_coeff(const struct setup_context *setup,1204const float (*vert)[4],1205struct tgsi_interp_coef *coef,1206uint vertSlot, uint i)1207{1208assert(i <= 3);1209coef->dadx[i] = 0.0F;1210coef->dady[i] = 0.0F;1211coef->a0[i] = vert[vertSlot][i] * vert[0][3];1212}121312141215/**1216* Do setup for point rasterization, then render the point.1217* Round or square points...1218* XXX could optimize a lot for 1-pixel points.1219*/1220void1221sp_setup_point(struct setup_context *setup,1222const float (*v0)[4])1223{1224struct softpipe_context *softpipe = setup->softpipe;1225const struct tgsi_shader_info *fsInfo = &setup->softpipe->fs_variant->info;1226const int sizeAttr = setup->softpipe->psize_slot;1227const float size1228= sizeAttr > 0 ? v0[sizeAttr][0]1229: setup->softpipe->rasterizer->point_size;1230const float halfSize = 0.5F * size;1231const boolean round = (boolean) setup->softpipe->rasterizer->point_smooth;1232const float x = v0[0][0]; /* Note: data[0] is always position */1233const float y = v0[0][1];1234const struct sp_setup_info *sinfo = &softpipe->setup_info;1235uint fragSlot;1236uint layer = 0;1237unsigned viewport_index = 0;1238#if DEBUG_VERTS1239debug_printf("Setup point:\n");1240print_vertex(setup, v0);1241#endif12421243assert(sinfo->valid);12441245if (unlikely(sp_debug & SP_DBG_NO_RAST) ||1246setup->softpipe->rasterizer->rasterizer_discard)1247return;12481249assert(setup->softpipe->reduced_prim == PIPE_PRIM_POINTS);12501251if (setup->softpipe->layer_slot > 0) {1252layer = *(unsigned *)v0[setup->softpipe->layer_slot];1253layer = MIN2(layer, setup->max_layer);1254}1255setup->quad[0].input.layer = layer;12561257if (setup->softpipe->viewport_index_slot > 0) {1258unsigned *udata = (unsigned*)v0[setup->softpipe->viewport_index_slot];1259viewport_index = sp_clamp_viewport_idx(*udata);1260}1261setup->quad[0].input.viewport_index = viewport_index;12621263/* For points, all interpolants are constant-valued.1264* However, for point sprites, we'll need to setup texcoords appropriately.1265* XXX: which coefficients are the texcoords???1266* We may do point sprites as textured quads...1267*1268* KW: We don't know which coefficients are texcoords - ultimately1269* the choice of what interpolation mode to use for each attribute1270* should be determined by the fragment program, using1271* per-attribute declaration statements that include interpolation1272* mode as a parameter. So either the fragment program will have1273* to be adjusted for pointsprite vs normal point behaviour, or1274* otherwise a special interpolation mode will have to be defined1275* which matches the required behaviour for point sprites. But -1276* the latter is not a feature of normal hardware, and as such1277* probably should be ruled out on that basis.1278*/1279setup->vprovoke = v0;12801281/* setup Z, W */1282const_coeff(setup, &setup->posCoef, 0, 2);1283const_coeff(setup, &setup->posCoef, 0, 3);12841285for (fragSlot = 0; fragSlot < fsInfo->num_inputs; fragSlot++) {1286const uint vertSlot = sinfo->attrib[fragSlot].src_index;1287uint j;12881289switch (sinfo->attrib[fragSlot].interp) {1290case SP_INTERP_CONSTANT:1291FALLTHROUGH;1292case SP_INTERP_LINEAR:1293for (j = 0; j < TGSI_NUM_CHANNELS; j++)1294const_coeff(setup, &setup->coef[fragSlot], vertSlot, j);1295break;1296case SP_INTERP_PERSPECTIVE:1297for (j = 0; j < TGSI_NUM_CHANNELS; j++)1298point_persp_coeff(setup, setup->vprovoke,1299&setup->coef[fragSlot], vertSlot, j);1300break;1301case SP_INTERP_POS:1302setup_fragcoord_coeff(setup, fragSlot);1303break;1304default:1305assert(0);1306}13071308if (fsInfo->input_semantic_name[fragSlot] == TGSI_SEMANTIC_FACE) {1309/* convert 0 to 1.0 and 1 to -1.0 */1310setup->coef[fragSlot].a0[0] = setup->facing * -2.0f + 1.0f;1311setup->coef[fragSlot].dadx[0] = 0.0;1312setup->coef[fragSlot].dady[0] = 0.0;1313}1314}131513161317if (halfSize <= 0.5 && !round) {1318/* special case for 1-pixel points */1319const int ix = ((int) x) & 1;1320const int iy = ((int) y) & 1;1321setup->quad[0].input.x0 = (int) x - ix;1322setup->quad[0].input.y0 = (int) y - iy;1323setup->quad[0].inout.mask = (1 << ix) << (2 * iy);1324clip_emit_quad(setup, &setup->quad[0]);1325}1326else {1327if (round) {1328/* rounded points */1329const int ixmin = block((int) (x - halfSize));1330const int ixmax = block((int) (x + halfSize));1331const int iymin = block((int) (y - halfSize));1332const int iymax = block((int) (y + halfSize));1333const float rmin = halfSize - 0.7071F; /* 0.7071 = sqrt(2)/2 */1334const float rmax = halfSize + 0.7071F;1335const float rmin2 = MAX2(0.0F, rmin * rmin);1336const float rmax2 = rmax * rmax;1337const float cscale = 1.0F / (rmax2 - rmin2);1338int ix, iy;13391340for (iy = iymin; iy <= iymax; iy += 2) {1341for (ix = ixmin; ix <= ixmax; ix += 2) {1342float dx, dy, dist2, cover;13431344setup->quad[0].inout.mask = 0x0;13451346dx = (ix + 0.5f) - x;1347dy = (iy + 0.5f) - y;1348dist2 = dx * dx + dy * dy;1349if (dist2 <= rmax2) {1350cover = 1.0F - (dist2 - rmin2) * cscale;1351setup->quad[0].input.coverage[QUAD_TOP_LEFT] = MIN2(cover, 1.0f);1352setup->quad[0].inout.mask |= MASK_TOP_LEFT;1353}13541355dx = (ix + 1.5f) - x;1356dy = (iy + 0.5f) - y;1357dist2 = dx * dx + dy * dy;1358if (dist2 <= rmax2) {1359cover = 1.0F - (dist2 - rmin2) * cscale;1360setup->quad[0].input.coverage[QUAD_TOP_RIGHT] = MIN2(cover, 1.0f);1361setup->quad[0].inout.mask |= MASK_TOP_RIGHT;1362}13631364dx = (ix + 0.5f) - x;1365dy = (iy + 1.5f) - y;1366dist2 = dx * dx + dy * dy;1367if (dist2 <= rmax2) {1368cover = 1.0F - (dist2 - rmin2) * cscale;1369setup->quad[0].input.coverage[QUAD_BOTTOM_LEFT] = MIN2(cover, 1.0f);1370setup->quad[0].inout.mask |= MASK_BOTTOM_LEFT;1371}13721373dx = (ix + 1.5f) - x;1374dy = (iy + 1.5f) - y;1375dist2 = dx * dx + dy * dy;1376if (dist2 <= rmax2) {1377cover = 1.0F - (dist2 - rmin2) * cscale;1378setup->quad[0].input.coverage[QUAD_BOTTOM_RIGHT] = MIN2(cover, 1.0f);1379setup->quad[0].inout.mask |= MASK_BOTTOM_RIGHT;1380}13811382if (setup->quad[0].inout.mask) {1383setup->quad[0].input.x0 = ix;1384setup->quad[0].input.y0 = iy;1385clip_emit_quad(setup, &setup->quad[0]);1386}1387}1388}1389}1390else {1391/* square points */1392const int xmin = (int) (x + 0.75 - halfSize);1393const int ymin = (int) (y + 0.25 - halfSize);1394const int xmax = xmin + (int) size;1395const int ymax = ymin + (int) size;1396/* XXX could apply scissor to xmin,ymin,xmax,ymax now */1397const int ixmin = block(xmin);1398const int ixmax = block(xmax - 1);1399const int iymin = block(ymin);1400const int iymax = block(ymax - 1);1401int ix, iy;14021403/*1404debug_printf("(%f, %f) -> X:%d..%d Y:%d..%d\n", x, y, xmin, xmax,ymin,ymax);1405*/1406for (iy = iymin; iy <= iymax; iy += 2) {1407uint rowMask = 0xf;1408if (iy < ymin) {1409/* above the top edge */1410rowMask &= (MASK_BOTTOM_LEFT | MASK_BOTTOM_RIGHT);1411}1412if (iy + 1 >= ymax) {1413/* below the bottom edge */1414rowMask &= (MASK_TOP_LEFT | MASK_TOP_RIGHT);1415}14161417for (ix = ixmin; ix <= ixmax; ix += 2) {1418uint mask = rowMask;14191420if (ix < xmin) {1421/* fragment is past left edge of point, turn off left bits */1422mask &= (MASK_BOTTOM_RIGHT | MASK_TOP_RIGHT);1423}1424if (ix + 1 >= xmax) {1425/* past the right edge */1426mask &= (MASK_BOTTOM_LEFT | MASK_TOP_LEFT);1427}14281429setup->quad[0].inout.mask = mask;1430setup->quad[0].input.x0 = ix;1431setup->quad[0].input.y0 = iy;1432clip_emit_quad(setup, &setup->quad[0]);1433}1434}1435}1436}1437}143814391440/**1441* Called by vbuf code just before we start buffering primitives.1442*/1443void1444sp_setup_prepare(struct setup_context *setup)1445{1446struct softpipe_context *sp = setup->softpipe;1447int i;1448unsigned max_layer = ~0;1449if (sp->dirty) {1450softpipe_update_derived(sp, sp->reduced_api_prim);1451}14521453/* Note: nr_attrs is only used for debugging (vertex printing) */1454setup->nr_vertex_attrs = draw_num_shader_outputs(sp->draw);14551456/*1457* Determine how many layers the fb has (used for clamping layer value).1458* OpenGL (but not d3d10) permits different amount of layers per rt, however1459* results are undefined if layer exceeds the amount of layers of ANY1460* attachment hence don't need separate per cbuf and zsbuf max.1461*/1462for (i = 0; i < setup->softpipe->framebuffer.nr_cbufs; i++) {1463struct pipe_surface *cbuf = setup->softpipe->framebuffer.cbufs[i];1464if (cbuf) {1465max_layer = MIN2(max_layer,1466cbuf->u.tex.last_layer - cbuf->u.tex.first_layer);14671468}1469}14701471/* Prepare pixel offset for rasterisation:1472* - pixel center (0.5, 0.5) for GL, or1473* - assume (0.0, 0.0) for other APIs.1474*/1475if (setup->softpipe->rasterizer->half_pixel_center) {1476setup->pixel_offset = 0.5f;1477} else {1478setup->pixel_offset = 0.0f;1479}14801481setup->max_layer = max_layer;14821483sp->quad.first->begin( sp->quad.first );14841485if (sp->reduced_api_prim == PIPE_PRIM_TRIANGLES &&1486sp->rasterizer->fill_front == PIPE_POLYGON_MODE_FILL &&1487sp->rasterizer->fill_back == PIPE_POLYGON_MODE_FILL) {1488/* we'll do culling */1489setup->cull_face = sp->rasterizer->cull_face;1490}1491else {1492/* 'draw' will do culling */1493setup->cull_face = PIPE_FACE_NONE;1494}1495}149614971498void1499sp_setup_destroy_context(struct setup_context *setup)1500{1501FREE( setup );1502}150315041505/**1506* Create a new primitive setup/render stage.1507*/1508struct setup_context *1509sp_setup_create_context(struct softpipe_context *softpipe)1510{1511struct setup_context *setup = CALLOC_STRUCT(setup_context);1512unsigned i;15131514setup->softpipe = softpipe;15151516for (i = 0; i < MAX_QUADS; i++) {1517setup->quad[i].coef = setup->coef;1518setup->quad[i].posCoef = &setup->posCoef;1519}15201521setup->span.left[0] = 1000000; /* greater than right[0] */1522setup->span.left[1] = 1000000; /* greater than right[1] */15231524return setup;1525}152615271528