diff options
author | Keith Whitwell <[email protected]> | 2007-09-25 13:20:53 +0100 |
---|---|---|
committer | Keith Whitwell <[email protected]> | 2007-09-25 13:23:20 +0100 |
commit | a37e0daeb97bb36ba10038b12a909e22e08b52c4 (patch) | |
tree | 6c8a10ceb9a7764a63db627f0602f552709631d2 /src/mesa/pipe | |
parent | 45b37dc078130b64e110d1a965960c60c56f0e60 (diff) |
First attempt at building vertex buffers post-clip.
Build a buffer of contigous vertices and indices at the backend of our
software transformation/clipping path. This will become the mechanism
for emitting buffers of vertices to rasterization hardware.
This is similar to but not the same as the post-transform vertex cache.
In particular, these vertices are subject to clipping, culling, poly offset,
etc. The vertices emitted will all be used by hardware.
TODOs include the actual transformation to hardware vertex formats, moving
this out of softpipe to somewhere more useful and allowing >1 primitive to
share the generated VB.
Diffstat (limited to 'src/mesa/pipe')
-rw-r--r-- | src/mesa/pipe/draw/draw_clip.c | 1 | ||||
-rw-r--r-- | src/mesa/pipe/draw/draw_prim.c | 9 | ||||
-rw-r--r-- | src/mesa/pipe/draw/draw_private.h | 6 | ||||
-rw-r--r-- | src/mesa/pipe/draw/draw_vertex_cache.c | 20 | ||||
-rw-r--r-- | src/mesa/pipe/draw/draw_vertex_shader.c | 2 | ||||
-rw-r--r-- | src/mesa/pipe/softpipe/Makefile | 1 | ||||
-rw-r--r-- | src/mesa/pipe/softpipe/sp_context.c | 14 | ||||
-rw-r--r-- | src/mesa/pipe/softpipe/sp_context.h | 2 | ||||
-rw-r--r-- | src/mesa/pipe/softpipe/sp_prim_setup.c | 96 | ||||
-rw-r--r-- | src/mesa/pipe/softpipe/sp_prim_setup.h | 34 | ||||
-rw-r--r-- | src/mesa/pipe/softpipe/sp_prim_vbuf.c | 305 |
11 files changed, 476 insertions, 14 deletions
diff --git a/src/mesa/pipe/draw/draw_clip.c b/src/mesa/pipe/draw/draw_clip.c index e2af69e0485..3ccc408bc0b 100644 --- a/src/mesa/pipe/draw/draw_clip.c +++ b/src/mesa/pipe/draw/draw_clip.c @@ -109,6 +109,7 @@ static void interp( const struct clipper *clip, dst->clipmask = 0; dst->edgeflag = 0; dst->pad = 0; + dst->vertex_id = 0; } /* Clip coordinates: interpolate normally diff --git a/src/mesa/pipe/draw/draw_prim.c b/src/mesa/pipe/draw/draw_prim.c index e82e48b90b8..be2f987b9a0 100644 --- a/src/mesa/pipe/draw/draw_prim.c +++ b/src/mesa/pipe/draw/draw_prim.c @@ -106,9 +106,12 @@ void draw_flush( struct draw_context *draw ) static struct prim_header *get_queued_prim( struct draw_context *draw, unsigned nr_verts ) { - if (draw->pq.queue_nr + 1 >= PRIM_QUEUE_LENGTH || - !draw_vertex_cache_check_space( draw, nr_verts )) - { + if (draw->pq.queue_nr + 1 >= PRIM_QUEUE_LENGTH) { +// fprintf(stderr, "p"); + draw_flush( draw ); + } + else if (!draw_vertex_cache_check_space( draw, nr_verts )) { +// fprintf(stderr, "v"); draw_flush( draw ); } diff --git a/src/mesa/pipe/draw/draw_private.h b/src/mesa/pipe/draw/draw_private.h index 1285f3200c8..04d38c4e0c2 100644 --- a/src/mesa/pipe/draw/draw_private.h +++ b/src/mesa/pipe/draw/draw_private.h @@ -54,7 +54,8 @@ struct vertex_header { unsigned clipmask:12; unsigned edgeflag:1; - unsigned pad:19; + unsigned pad:3; + unsigned vertex_id:16; float clip[4]; @@ -230,6 +231,8 @@ extern int draw_vertex_cache_check_space( struct draw_context *draw, extern void draw_vertex_cache_validate( struct draw_context *draw ); extern void draw_vertex_cache_invalidate( struct draw_context *draw ); extern void draw_vertex_cache_unreference( struct draw_context *draw ); +extern void draw_vertex_cache_reset_vertex_ids( struct draw_context *draw ); + extern void draw_vertex_shader_queue_flush( struct draw_context *draw ); @@ -255,6 +258,7 @@ dup_vert( struct draw_stage *stage, { struct vertex_header *tmp = stage->tmp[idx]; memcpy(tmp, vert, stage->draw->vertex_info.size * sizeof(float) ); + tmp->vertex_id = ~0; return tmp; } diff --git a/src/mesa/pipe/draw/draw_vertex_cache.c b/src/mesa/pipe/draw/draw_vertex_cache.c index f1b0cb14bd3..a226798123c 100644 --- a/src/mesa/pipe/draw/draw_vertex_cache.c +++ b/src/mesa/pipe/draw/draw_vertex_cache.c @@ -46,6 +46,8 @@ void draw_vertex_cache_invalidate( struct draw_context *draw ) for (i = 0; i < Elements( draw->vcache.idx ); i++) draw->vcache.idx[i] = ~0; + +// fprintf(stderr, "x\n"); } @@ -63,10 +65,14 @@ static struct vertex_header *get_vertex( struct draw_context *draw, /* If slot is in use, use the overflow area: */ - if (draw->vcache.referenced & (1 << slot)) + if (draw->vcache.referenced & (1 << slot)) { +// fprintf(stderr, "o"); slot = VCACHE_SIZE + draw->vcache.overflow++; - else + } + else { +// fprintf(stderr, "."); draw->vcache.referenced |= (1 << slot); /* slot now in use */ + } draw->vcache.idx[slot] = i; @@ -79,7 +85,10 @@ static struct vertex_header *get_vertex( struct draw_context *draw, /* Need to set the vertex's edge flag here. If we're being called * by do_ef_triangle(), that function needs edge flag info! */ + draw->vcache.vertex[slot]->clipmask = 0; draw->vcache.vertex[slot]->edgeflag = 1; /*XXX use user's edge flag! */ + draw->vcache.vertex[slot]->pad = 0; + draw->vcache.vertex[slot]->vertex_id = ~0; } return draw->vcache.vertex[slot]; @@ -110,6 +119,13 @@ static struct vertex_header *get_ubyte_elt_vertex( struct draw_context *draw, } +void draw_vertex_cache_reset_vertex_ids( struct draw_context *draw ) +{ + unsigned i; + + for (i = 0; i < Elements(draw->vcache.vertex); i++) + draw->vcache.vertex[i]->vertex_id = ~0; +} void draw_vertex_cache_validate( struct draw_context *draw ) { diff --git a/src/mesa/pipe/draw/draw_vertex_shader.c b/src/mesa/pipe/draw/draw_vertex_shader.c index ef0399c46e5..fe4f124dd2f 100644 --- a/src/mesa/pipe/draw/draw_vertex_shader.c +++ b/src/mesa/pipe/draw/draw_vertex_shader.c @@ -189,6 +189,8 @@ void draw_vertex_shader_queue_flush( struct draw_context *draw ) { unsigned i, j; +// fprintf(stderr, " q(%d) ", draw->vs.queue_nr ); + /* run vertex shader on vertex cache entries, four per invokation */ for (i = 0; i < draw->vs.queue_nr; i += 4) { struct vertex_header *dests[4]; diff --git a/src/mesa/pipe/softpipe/Makefile b/src/mesa/pipe/softpipe/Makefile index 0335f56b99e..63eb6b57615 100644 --- a/src/mesa/pipe/softpipe/Makefile +++ b/src/mesa/pipe/softpipe/Makefile @@ -10,6 +10,7 @@ DRIVER_SOURCES = \ sp_context.c \ sp_draw_arrays.c \ sp_prim_setup.c \ + sp_prim_vbuf.c \ sp_quad.c \ sp_quad_alpha_test.c \ sp_quad_blend.c \ diff --git a/src/mesa/pipe/softpipe/sp_context.c b/src/mesa/pipe/softpipe/sp_context.c index a6ab45314cf..e415966dae1 100644 --- a/src/mesa/pipe/softpipe/sp_context.c +++ b/src/mesa/pipe/softpipe/sp_context.c @@ -334,7 +334,19 @@ struct pipe_context *softpipe_create( struct pipe_winsys *pipe_winsys, */ softpipe->draw = draw_create(); assert(softpipe->draw); - draw_set_rasterize_stage(softpipe->draw, sp_draw_render_stage(softpipe)); + softpipe->setup = sp_draw_render_stage(softpipe); + + if (getenv("SP_VBUF")) { + softpipe->vbuf = sp_draw_vbuf_stage(softpipe->draw, + &softpipe->pipe, + sp_vbuf_setup_draw); + + draw_set_rasterize_stage(softpipe->draw, softpipe->vbuf); + } + else { + draw_set_rasterize_stage(softpipe->draw, softpipe->setup); + } + sp_init_region_functions(softpipe); sp_init_surface_functions(softpipe); diff --git a/src/mesa/pipe/softpipe/sp_context.h b/src/mesa/pipe/softpipe/sp_context.h index 7b1518cfaca..c0a681f3d6b 100644 --- a/src/mesa/pipe/softpipe/sp_context.h +++ b/src/mesa/pipe/softpipe/sp_context.h @@ -146,6 +146,8 @@ struct softpipe_context { /** The primitive drawing context */ struct draw_context *draw; + struct draw_stage *setup; + struct draw_stage *vbuf; struct pipe_surface *cbuf; /**< current color buffer (one of cbufs) */ }; diff --git a/src/mesa/pipe/softpipe/sp_prim_setup.c b/src/mesa/pipe/softpipe/sp_prim_setup.c index 6d63cc94123..2e27d00acfb 100644 --- a/src/mesa/pipe/softpipe/sp_prim_setup.c +++ b/src/mesa/pipe/softpipe/sp_prim_setup.c @@ -41,7 +41,7 @@ #include "pipe/draw/draw_vertex.h" #include "pipe/p_util.h" - +#define DEBUG_VERTS 0 /** * Triangle edge info @@ -241,15 +241,15 @@ static void flush_spans( struct setup_stage *setup ) setup->span.right[1] = 0; } -#if 0 +#if DEBUG_VERTS static void print_vertex(const struct setup_stage *setup, const struct vertex_header *v) { int i; - printf("Vertex:\n"); + fprintf(stderr, "Vertex: (%p)\n", v); for (i = 0; i < setup->quad.nr_attrs; i++) { - printf(" %d: %f %f %f\n", i, - v->data[i][0], v->data[i][1], v->data[i][2]); + fprintf(stderr, " %d: %f %f %f %f\n", i, + v->data[i][0], v->data[i][1], v->data[i][2], v->data[i][3]); } } #endif @@ -261,8 +261,8 @@ static boolean setup_sort_vertices( struct setup_stage *setup, const struct vertex_header *v1 = prim->v[1]; const struct vertex_header *v2 = prim->v[2]; -#if 0 - printf("Triangle:\n"); +#if DEBUG_VERTS + fprintf(stderr, "Triangle:\n"); print_vertex(setup, v0); print_vertex(setup, v1); print_vertex(setup, v2); @@ -1096,3 +1096,85 @@ struct draw_stage *sp_draw_render_stage( struct softpipe_context *softpipe ) return &setup->stage; } + + +/* Recalculate det. This is only used in the test harness below: + */ +static void calc_det( struct prim_header *header ) +{ + /* Window coords: */ + const float *v0 = header->v[0]->data[0]; + const float *v1 = header->v[1]->data[0]; + const float *v2 = header->v[2]->data[0]; + + /* edge vectors e = v0 - v2, f = v1 - v2 */ + const float ex = v0[0] - v2[0]; + const float ey = v0[1] - v2[1]; + const float fx = v1[0] - v2[0]; + const float fy = v1[1] - v2[1]; + + /* det = cross(e,f).z */ + header->det = ex * fy - ey * fx; +} + + + +/* Test harness - feed vertex buffer back into prim pipeline. + * + * The big issue at this point is that reset_stipple doesn't make it + * through the interface. Probably need to split primitives at reset + * stipple, perhaps using the ~0 index marker. + */ +void sp_vbuf_setup_draw( struct pipe_context *pipe, + unsigned primitive, + const ushort *elements, + unsigned nr_elements, + const void *vertex_buffer, + unsigned nr_vertices ) +{ + struct softpipe_context *softpipe = softpipe_context( pipe ); + struct setup_stage *setup = setup_stage( softpipe->setup ); + struct prim_header prim; + unsigned vertex_size = setup->stage.draw->vertex_info.size * sizeof(float); + int i, j; + + prim.det = 0; + prim.reset_line_stipple = 0; + prim.edgeflags = 0; + prim.pad = 0; + + setup->stage.begin( &setup->stage ); + + switch (primitive) { + case PIPE_PRIM_TRIANGLES: + for (i = 0; i < nr_elements; i += 3) { + for (j = 0; j < 3; j++) + prim.v[j] = (struct vertex_header *)((char *)vertex_buffer + + elements[i+j] * vertex_size); + + calc_det(&prim); + setup->stage.tri( &setup->stage, &prim ); + } + break; + + case PIPE_PRIM_LINES: + for (i = 0; i < nr_elements; i += 2) { + for (j = 0; j < 2; j++) + prim.v[j] = (struct vertex_header *)((char *)vertex_buffer + + elements[i+j] * vertex_size); + + setup->stage.line( &setup->stage, &prim ); + } + break; + + case PIPE_PRIM_POINTS: + for (i = 0; i < nr_elements; i += 2) { + prim.v[i] = (struct vertex_header *)((char *)vertex_buffer + + elements[i] * vertex_size); + setup->stage.point( &setup->stage, &prim ); + } + break; + } + + setup->stage.end( &setup->stage ); +} diff --git a/src/mesa/pipe/softpipe/sp_prim_setup.h b/src/mesa/pipe/softpipe/sp_prim_setup.h index dece42fe457..598394f73ee 100644 --- a/src/mesa/pipe/softpipe/sp_prim_setup.h +++ b/src/mesa/pipe/softpipe/sp_prim_setup.h @@ -44,5 +44,39 @@ struct softpipe_context; extern struct draw_stage *sp_draw_render_stage( struct softpipe_context *softpipe ); +/* A special stage to gather the stream of triangles, lines, points + * together and reconstruct vertex buffers for hardware upload. + * + * First attempt, work in progress. + * + * TODO: + * - separate out vertex buffer building and primitive emit, ie >1 draw per vb. + * - tell vbuf stage how to build hw vertices directly + * - pass vbuf stage a buffer pointer for direct emit to agp/vram. + */ +typedef void (*vbuf_draw_func)( struct pipe_context *pipe, + unsigned prim, + const ushort *elements, + unsigned nr_elements, + const void *vertex_buffer, + unsigned nr_vertices ); + + +extern struct draw_stage *sp_draw_vbuf_stage( struct draw_context *draw_context, + struct pipe_context *pipe, + vbuf_draw_func draw ); + + + +/* Test harness + */ +void sp_vbuf_setup_draw( struct pipe_context *pipe, + unsigned prim, + const ushort *elements, + unsigned nr_elements, + const void *vertex_buffer, + unsigned nr_vertices ); + + #endif /* SP_PRIM_SETUP_H */ diff --git a/src/mesa/pipe/softpipe/sp_prim_vbuf.c b/src/mesa/pipe/softpipe/sp_prim_vbuf.c new file mode 100644 index 00000000000..235903ca6d5 --- /dev/null +++ b/src/mesa/pipe/softpipe/sp_prim_vbuf.c @@ -0,0 +1,305 @@ +/************************************************************************** + * + * Copyright 2007 Tungsten Graphics, Inc., Cedar Park, Texas. + * 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 TUNGSTEN GRAPHICS 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. + * + **************************************************************************/ + +/** + * Build post-transformation, post-clipping vertex buffers and element + * lists by hooking into the end of the primitive pipeline and + * manipulating the vertex_id field in the vertex headers. + * + * Keith Whitwell <[email protected]> + */ + + +#include "sp_context.h" +#include "sp_headers.h" +#include "sp_quad.h" +#include "sp_prim_setup.h" +#include "pipe/draw/draw_private.h" +#include "pipe/draw/draw_vertex.h" +#include "pipe/p_util.h" + +static void vbuf_flush_elements( struct draw_stage *stage ); + + +#define VBUF_SIZE (64*1024) +#define IBUF_SIZE (16*1024) + + +/** + * Vertex buffer emit stage. + */ +struct vbuf_stage { + struct draw_stage stage; /**< This must be first (base class) */ + + struct draw_context *draw_context; + struct pipe_context *pipe; + vbuf_draw_func draw; + + /* Vertices are passed in as an array of floats making up each + * attribute in turn. Will eventually convert to hardware format + * in this stage. + */ + char *vertex_map; + char *vertex_ptr; + unsigned vertex_size; + unsigned nr_vertices; + + unsigned max_vertices; + + ushort *element_map; + unsigned nr_elements; + + unsigned prim; + +}; + +/** + * Basically a cast wrapper. + */ +static INLINE struct vbuf_stage *vbuf_stage( struct draw_stage *stage ) +{ + return (struct vbuf_stage *)stage; +} + + + + +static boolean overflow( void *map, void *ptr, unsigned bytes, unsigned bufsz ) +{ + unsigned long used = (char *)ptr - (char *)map; + return (used + bytes) > bufsz; +} + + +static boolean check_space( struct vbuf_stage *vbuf ) +{ + if (overflow( vbuf->vertex_map, + vbuf->vertex_ptr, + 4 * vbuf->vertex_size, + VBUF_SIZE )) + return FALSE; + + + if (vbuf->nr_elements + 4 > IBUF_SIZE / sizeof(ushort) ) + return FALSE; + + return TRUE; +} + + +static void emit_vertex( struct vbuf_stage *vbuf, + struct vertex_header *vertex ) +{ + fprintf(stderr, "emit vertex %d to %p\n", + vbuf->nr_vertices, vbuf->vertex_ptr); + + vertex->vertex_id = vbuf->nr_vertices++; + + //vbuf->emit_vertex( vbuf->vertex_ptr, vertex ); + memcpy(vbuf->vertex_ptr, vertex, vbuf->vertex_size); + + vbuf->vertex_ptr += vbuf->vertex_size; +} + + + +/** + * + */ +static void vbuf_tri( struct draw_stage *stage, + struct prim_header *prim ) +{ + struct vbuf_stage *vbuf = vbuf_stage( stage ); + unsigned i; + + if (!check_space( vbuf )) + vbuf_flush_elements( stage ); + + for (i = 0; i < 3; i++) { + if (prim->v[i]->vertex_id == 0xffff) + emit_vertex( vbuf, prim->v[i] ); + + vbuf->element_map[vbuf->nr_elements++] = prim->v[i]->vertex_id; + } +} + + +static void vbuf_line(struct draw_stage *stage, + struct prim_header *prim) +{ + struct vbuf_stage *vbuf = vbuf_stage( stage ); + unsigned i; + + if (!check_space( vbuf )) + vbuf_flush_elements( stage ); + + for (i = 0; i < 2; i++) { + if (prim->v[i]->vertex_id == 0xffff) + emit_vertex( vbuf, prim->v[i] ); + + vbuf->element_map[vbuf->nr_elements++] = prim->v[i]->vertex_id; + } +} + + +static void vbuf_point(struct draw_stage *stage, + struct prim_header *prim) +{ + struct vbuf_stage *vbuf = vbuf_stage( stage ); + + if (!check_space( vbuf )) + vbuf_flush_elements( stage ); + + if (prim->v[0]->vertex_id == 0xffff) + emit_vertex( vbuf, prim->v[0] ); + + vbuf->element_map[vbuf->nr_elements++] = prim->v[0]->vertex_id; +} + + +static void vbuf_first_tri( struct draw_stage *stage, + struct prim_header *prim ) +{ + struct vbuf_stage *vbuf = vbuf_stage( stage ); + + vbuf_flush_elements( stage ); + stage->tri = vbuf_tri; + stage->tri( stage, prim ); + vbuf->prim = PIPE_PRIM_TRIANGLES; +} + +static void vbuf_first_line( struct draw_stage *stage, + struct prim_header *prim ) +{ + struct vbuf_stage *vbuf = vbuf_stage( stage ); + + vbuf_flush_elements( stage ); + stage->line = vbuf_line; + stage->line( stage, prim ); + vbuf->prim = PIPE_PRIM_LINES; +} + +static void vbuf_first_point( struct draw_stage *stage, + struct prim_header *prim ) +{ + struct vbuf_stage *vbuf = vbuf_stage( stage ); + + vbuf_flush_elements( stage ); + stage->point = vbuf_point; + stage->point( stage, prim ); + vbuf->prim = PIPE_PRIM_POINTS; +} + + + +static void vbuf_flush_elements( struct draw_stage *stage ) +{ + struct vbuf_stage *vbuf = vbuf_stage( stage ); + + if (vbuf->nr_elements) { + fprintf(stderr, "%s (%d elts)\n", __FUNCTION__, vbuf->nr_elements); + + /* Draw now or add to list of primitives??? + */ + vbuf->draw( vbuf->pipe, + vbuf->prim, + vbuf->element_map, + vbuf->nr_elements, + vbuf->vertex_map, + (vbuf->vertex_ptr - vbuf->vertex_map) / vbuf->vertex_size ); + + vbuf->nr_elements = 0; + + vbuf->vertex_ptr = vbuf->vertex_map; + vbuf->nr_vertices = 0; + + /* Reset vertex ids? Actually, want to not do that unless our + * vertex buffer is full. Would like separate + * flush-on-index-full and flush-on-vb-full, but may raise + * issues uploading vertices if the hardware wants to flush when + * we flush. + */ + draw_vertex_cache_reset_vertex_ids( vbuf->draw_context ); + } + + stage->tri = vbuf_first_tri; + stage->line = vbuf_first_line; + stage->point = vbuf_first_point; +} + + +static void vbuf_begin( struct draw_stage *stage ) +{ + struct vbuf_stage *vbuf = vbuf_stage(stage); + + vbuf->vertex_size = vbuf->draw_context->vertex_info.size * sizeof(float); +} + + +static void vbuf_end( struct draw_stage *stage ) +{ + /* Overkill. + */ + vbuf_flush_elements( stage ); +} + + +static void reset_stipple_counter( struct draw_stage *stage ) +{ + /* XXX: This doesn't work. + */ +} + + +/** + * Create a new primitive vbuf/render stage. + */ +struct draw_stage *sp_draw_vbuf_stage( struct draw_context *draw_context, + struct pipe_context *pipe, + vbuf_draw_func draw ) +{ + struct vbuf_stage *vbuf = CALLOC_STRUCT(vbuf_stage); + + vbuf->stage.begin = vbuf_begin; + vbuf->stage.point = vbuf_first_point; + vbuf->stage.line = vbuf_first_line; + vbuf->stage.tri = vbuf_first_tri; + vbuf->stage.end = vbuf_end; + vbuf->stage.reset_stipple_counter = reset_stipple_counter; + + vbuf->pipe = pipe; + vbuf->draw = draw; + vbuf->draw_context = draw_context; + + vbuf->element_map = malloc( IBUF_SIZE ); + vbuf->vertex_map = malloc( VBUF_SIZE ); + + vbuf->vertex_ptr = vbuf->vertex_map; + + + return &vbuf->stage; +} |