From 03aff23227152a913a5cbafd6590a3698c725d39 Mon Sep 17 00:00:00 2001 From: Gert Wollny Date: Mon, 8 Aug 2022 13:58:50 +0200 Subject: [PATCH 1/8] shader: Fix use of out-of-scope data A pointer to new_srcs is used outside of the scope that new_srcs is declared in, so move the declaration into the scope where it is actually used. Signed-off-by: Gert Wollny Reviewed-by: Italo Nicola Part-of: --- src/vrend_shader.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vrend_shader.c b/src/vrend_shader.c index d7f95f2e8..56acff1c3 100644 --- a/src/vrend_shader.c +++ b/src/vrend_shader.c @@ -3180,13 +3180,14 @@ static void translate_tex(struct dump_ctx *ctx, } } + char buf[255]; + const char *new_srcs[4] = { buf, srcs[1], srcs[2], srcs[3] }; + /* We have to unnormalize the coordinate for all but the texel fetch instruction */ if (inst->Instruction.Opcode != TGSI_OPCODE_TXF && vrend_shader_sampler_views_mask_get(ctx->key->sampler_views_emulated_rect_mask, sinfo->sreg_index)) { - char buf[255]; const char *bias = ""; - const char *new_srcs[4] = { buf, srcs[1], srcs[2], srcs[3] }; /* No LOD for these texture types, but on GLES we emulate RECT by using * a normal 2D texture, so we have to give LOD 0 */ -- GitLab From f98c3d190de07834386e701b5d7c6e808e7aeef9 Mon Sep 17 00:00:00 2001 From: Gert Wollny Date: Mon, 8 Aug 2022 14:21:09 +0200 Subject: [PATCH 2/8] vrend: Keep the info about the number of patch varyings Signed-off-by: Gert Wollny Reviewed-by: Italo Nicola Part-of: --- src/vrend_renderer.c | 4 ++++ src/vrend_shader.h | 1 + 2 files changed, 5 insertions(+) diff --git a/src/vrend_renderer.c b/src/vrend_renderer.c index 3337f75a9..fa76e30b0 100644 --- a/src/vrend_renderer.c +++ b/src/vrend_renderer.c @@ -342,6 +342,7 @@ struct global_renderer_state { uint32_t max_texture_2d_size; uint32_t max_texture_3d_size; uint32_t max_texture_cube_size; + uint32_t max_shader_patch_varyings; /* inferred GL caching type */ uint32_t inferred_gl_caching_type; @@ -7227,6 +7228,7 @@ struct vrend_context *vrend_create_context(int id, uint32_t nlen, const char *de grctx->res_hash = vrend_ctx_resource_init_table(); list_inithead(&grctx->untyped_resources); + grctx->shader_cfg.max_shader_patch_varyings = vrend_state.max_shader_patch_varyings; grctx->shader_cfg.use_gles = vrend_state.use_gles; grctx->shader_cfg.use_core_profile = vrend_state.use_core_profile; grctx->shader_cfg.use_explicit_locations = vrend_state.use_explicit_locations; @@ -11208,6 +11210,8 @@ static void vrend_renderer_fill_caps_v2(int gl_ver, int gles_ver, union virgl_c } else caps->v2.max_shader_patch_varyings = 0; + vrend_state.max_shader_patch_varyings = caps->v2.max_shader_patch_varyings; + if (has_feature(feat_texture_gather)) { glGetIntegerv(GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET, &caps->v2.min_texture_gather_offset); glGetIntegerv(GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET, &caps->v2.max_texture_gather_offset); diff --git a/src/vrend_shader.h b/src/vrend_shader.h index 4a4d715a2..754d05e8c 100644 --- a/src/vrend_shader.h +++ b/src/vrend_shader.h @@ -220,6 +220,7 @@ struct vrend_shader_key { struct vrend_shader_cfg { uint32_t glsl_version : 12; uint32_t max_draw_buffers : 4; + uint32_t max_shader_patch_varyings : 6; uint32_t use_gles : 1; uint32_t use_core_profile : 1; uint32_t use_explicit_locations : 1; -- GitLab From e6ecfeebc8b47d76c7d02017c1ba9921cac493fe Mon Sep 17 00:00:00 2001 From: Gert Wollny Date: Wed, 3 Aug 2022 16:03:56 +0200 Subject: [PATCH 3/8] shader: Stabilize the location assignment While we can always assume that the number of varyings is 32, the number of patch varyings might be lower, so instead of counting down for patches and counting up for generics, do the opposite. In addition, use a better formatting of the emitted glsl around this declaration. Since we no longer patch the IO variables, there is no need to format this to some specific pattern. Signed-off-by: Gert Wollny Reviewed-by: Italo Nicola Part-of: --- src/vrend_shader.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vrend_shader.c b/src/vrend_shader.c index 56acff1c3..8e94c4195 100644 --- a/src/vrend_shader.c +++ b/src/vrend_shader.c @@ -6475,13 +6475,13 @@ emit_ios_generic(const struct dump_ctx *ctx, if (ctx->separable_program && io->name == TGSI_SEMANTIC_GENERIC && !(ctx->prog_type == TGSI_PROCESSOR_FRAGMENT && strcmp(inout, "in") != 0)) { - snprintf(layout, sizeof(layout), "layout(location = %d)\n", io->sid); + snprintf(layout, sizeof(layout), "layout(location = %d) ", 31 - io->sid); } if (io->first == io->last) { emit_hdr(glsl_strbufs, layout); /* ugly leave spaces to patch interp in later */ - emit_hdrf(glsl_strbufs, "%s%s\n%s %s %s %s%s;\n", + emit_hdrf(glsl_strbufs, "%s%s %s %s %s %s%s;\n", io->precise ? "precise" : "", io->invariant ? "invariant" : "", prefix, @@ -6670,7 +6670,7 @@ emit_ios_patch(struct vrend_glsl_strbufs *glsl_strbufs, /* We start these locations from 32 and proceed downwards, to avoid * conflicting with generic IO locations. */ if (emit_location) - emit_hdrf(glsl_strbufs, "layout(location = %d) ", 32 - io->sid); + emit_hdrf(glsl_strbufs, "layout(location = %d) ", io->sid); if (io->last == io->first) { emit_hdrf(glsl_strbufs, "%s %s vec4 %s;\n", prefix, inout, io->glsl_name); -- GitLab From 4f2667ff1d4076de75e543c3658c149e82261e46 Mon Sep 17 00:00:00 2001 From: Gert Wollny Date: Thu, 4 Aug 2022 18:07:04 +0200 Subject: [PATCH 4/8] vrend: scan shader for ability to be emitted as separable_program Since the property separable_program is used in the shader key evaluation, query the tokens before running the shader selection for the first time. In addition, check whether the IO semantics of inter-stage varyings used by the shader allow emitting the shader as SSO. v2: Fix ws errors (Italo) Signed-off-by: Gert Wollny Reviewed-by: Italo Nicola Part-of: --- src/vrend_renderer.c | 12 +++--- src/vrend_shader.c | 99 ++++++++++++++++++++++++++++++++++++++++++++ src/vrend_shader.h | 3 ++ 3 files changed, 107 insertions(+), 7 deletions(-) diff --git a/src/vrend_renderer.c b/src/vrend_renderer.c index fa76e30b0..702f8668e 100644 --- a/src/vrend_renderer.c +++ b/src/vrend_renderer.c @@ -4019,15 +4019,13 @@ static int vrend_finish_shader(struct vrend_context *ctx, struct vrend_shader_selector *sel, const struct tgsi_token *tokens) { - int r; - sel->tokens = tgsi_dup_tokens(tokens); - r = vrend_shader_select(ctx->sub, sel, NULL); - if (r) { - return EINVAL; - } - return 0; + if (!ctx->shader_cfg.use_gles && sel->type != PIPE_SHADER_COMPUTE) + sel->sinfo.separable_program = + vrend_shader_query_separable_program(sel->tokens, &ctx->shader_cfg); + + return vrend_shader_select(ctx->sub, sel, NULL) ? EINVAL : 0; } int vrend_create_shader(struct vrend_context *ctx, diff --git a/src/vrend_shader.c b/src/vrend_shader.c index 8e94c4195..2f744e81b 100644 --- a/src/vrend_shader.c +++ b/src/vrend_shader.c @@ -80,6 +80,8 @@ #define FRONT_COLOR_EMITTED (1 << 0) #define BACK_COLOR_EMITTED (1 << 1); +#define MAX_VARYING 32 + enum vrend_sysval_uniform { UNIFORM_WINSYS_ADJUST_Y, UNIFORM_CLIP_PLANE, @@ -7533,6 +7535,102 @@ static int compare_sid(const void *lhs, const void *rhs) return l->sid - r->sid; } +struct sso_scan_ctx { + struct tgsi_iterate_context iter; + const struct vrend_shader_cfg *cfg; + uint8_t max_generic_in_sid; + uint8_t max_patch_in_sid; + uint8_t max_generic_out_sid; + uint8_t max_patch_out_sid; + bool separable_program; + bool unsupported_io; +}; + +static boolean +iter_prop_for_separable(struct tgsi_iterate_context *iter, + struct tgsi_full_property *prop) +{ + struct sso_scan_ctx *ctx = (struct sso_scan_ctx *) iter; + + if (prop->Property.PropertyName == TGSI_PROPERTY_SEPARABLE_PROGRAM) + ctx->separable_program = prop->u[0].Data != 0; + return true; +} + +static boolean +iter_decl_for_overlap(struct tgsi_iterate_context *iter, + struct tgsi_full_declaration *decl) +{ + struct sso_scan_ctx *ctx = (struct sso_scan_ctx *) iter; + + /* VS inputs and FS outputs are of no interest + * when it comes to IO matching */ + if (decl->Declaration.File == TGSI_FILE_INPUT && + iter->processor.Processor == TGSI_PROCESSOR_VERTEX) + return true; + + if (decl->Declaration.File == TGSI_FILE_OUTPUT && + iter->processor.Processor == TGSI_PROCESSOR_FRAGMENT) + return true; + + switch (decl->Semantic.Name) { + case TGSI_SEMANTIC_PATCH: + if (decl->Declaration.File == TGSI_FILE_INPUT) { + if (ctx->max_patch_in_sid < decl->Semantic.Index) + ctx->max_patch_in_sid = decl->Semantic.Index; + } else { + if (ctx->max_patch_out_sid < decl->Semantic.Index) + ctx->max_patch_out_sid = decl->Semantic.Index; + } + break; + case TGSI_SEMANTIC_GENERIC: + if (decl->Declaration.File == TGSI_FILE_INPUT) { + if (ctx->max_generic_in_sid < decl->Semantic.Index) + ctx->max_generic_in_sid = decl->Semantic.Index; + } else { + if (ctx->max_generic_out_sid < decl->Semantic.Index) + ctx->max_generic_out_sid = decl->Semantic.Index; + } + break; + case TGSI_SEMANTIC_COLOR: + case TGSI_SEMANTIC_CLIPVERTEX: + case TGSI_SEMANTIC_BCOLOR: + case TGSI_SEMANTIC_TEXCOORD: + case TGSI_SEMANTIC_FOG: + /* These are semantics that need to be matched by name and since we can't + * guarantee that they exist in all the stages of separable shaders + * we can't emit the shader as SSO */ + ctx->unsupported_io = true; + break; + default: + ; + } + return true; +} + + +bool vrend_shader_query_separable_program(const struct tgsi_token *tokens, + const struct vrend_shader_cfg *cfg) +{ + struct sso_scan_ctx ctx = {0}; + ctx.cfg = cfg; + ctx.iter.iterate_property = iter_prop_for_separable; + ctx.iter.iterate_declaration = iter_decl_for_overlap; + tgsi_iterate_shader(tokens, &ctx.iter); + + /* Since we have to match by location, and have to handle generics and patches + * at in the limited range of 32 locations, we have to make sure that the + * the generics range and the patch range don't overlap. In addition, to + * work around that radeonsi doesn't support patch locations above 30 we have + * to check that limit too. */ + bool supports_separable = !ctx.unsupported_io && + (ctx.max_generic_in_sid + ctx.max_patch_in_sid < MAX_VARYING) && + (ctx.max_generic_out_sid + ctx.max_patch_out_sid < MAX_VARYING) && + (ctx.max_patch_in_sid < ctx.cfg->max_shader_patch_varyings) && + (ctx.max_patch_out_sid < ctx.cfg->max_shader_patch_varyings); + return ctx.separable_program && supports_separable; +} + bool vrend_convert_shader(const struct vrend_context *rctx, const struct vrend_shader_cfg *cfg, const struct tgsi_token *tokens, @@ -7546,6 +7644,7 @@ bool vrend_convert_shader(const struct vrend_context *rctx, boolean bret; memset(&ctx, 0, sizeof(struct dump_ctx)); + ctx.cfg = cfg; /* First pass to deal with edge cases. */ ctx.iter.iterate_declaration = iter_decls; diff --git a/src/vrend_shader.h b/src/vrend_shader.h index 754d05e8c..05bfbd9f5 100644 --- a/src/vrend_shader.h +++ b/src/vrend_shader.h @@ -266,6 +266,9 @@ bool vrend_shader_create_passthrough_tcs(const struct vrend_context *ctx, bool vrend_shader_needs_alpha_func(const struct vrend_shader_key *key); +bool vrend_shader_query_separable_program(const struct tgsi_token *tokens, + const struct vrend_shader_cfg *cfg); + static inline bool vrend_shader_sampler_views_mask_get( const uint64_t mask[static VREND_SHADER_SAMPLER_VIEWS_MASK_LENGTH], int index) -- GitLab From 60380fe56c88b260ef162e79c757de98ec66b6e6 Mon Sep 17 00:00:00 2001 From: Gert Wollny Date: Wed, 3 Aug 2022 16:02:41 +0200 Subject: [PATCH 5/8] vrend: Don't update FS shader key parts if we link SSO Here we match by using the location, so the interpolation specifiers should not be needed. vs: Fix ws errors Signed-off-by: Gert Wollny Reviewed-by: Italo Nicola Part-of: --- src/vrend_renderer.c | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/vrend_renderer.c b/src/vrend_renderer.c index 702f8668e..6484979bc 100644 --- a/src/vrend_renderer.c +++ b/src/vrend_renderer.c @@ -3812,27 +3812,27 @@ static inline void vrend_sync_shader_io(struct vrend_sub_context *sub_ctx, key->require_output_arrays = next->sinfo.has_input_arrays; key->out_generic_expected_mask = next->sinfo.in_generic_emitted_mask; key->out_texcoord_expected_mask = next->sinfo.in_texcoord_emitted_mask; - } - - /* FS gets the clip/cull info in the key from this shader, so - * we can avoid re-translating this shader by not updating the - * info in the key */ - if (next_type != PIPE_SHADER_FRAGMENT) { - key->num_out_clip = sub_ctx->shaders[next_type]->current->var_sinfo.num_in_clip; - key->num_out_cull = sub_ctx->shaders[next_type]->current->var_sinfo.num_in_cull; - } - if (next_type == PIPE_SHADER_FRAGMENT) { - struct vrend_shader *fs = - sub_ctx->shaders[PIPE_SHADER_FRAGMENT]->current; - key->fs_info = fs->var_sinfo.fs_info; - if (type == PIPE_SHADER_VERTEX && sub_ctx->shaders[type]) { - uint32_t fog_input = sub_ctx->shaders[next_type]->sinfo.fog_input_mask; - uint32_t fog_output = sub_ctx->shaders[type]->sinfo.fog_output_mask; + /* FS gets the clip/cull info in the key from this shader, so + * we can avoid re-translating this shader by not updating the + * info in the key */ + if (next_type != PIPE_SHADER_FRAGMENT) { + key->num_out_clip = sub_ctx->shaders[next_type]->current->var_sinfo.num_in_clip; + key->num_out_cull = sub_ctx->shaders[next_type]->current->var_sinfo.num_in_cull; + } - // We only want to issue the fixup for inputs not fed by - // the outputs of the previous stage - key->vs.fog_fixup_mask = (fog_input ^ fog_output) & fog_input; + if (next_type == PIPE_SHADER_FRAGMENT) { + struct vrend_shader *fs = + sub_ctx->shaders[PIPE_SHADER_FRAGMENT]->current; + key->fs_info = fs->var_sinfo.fs_info; + if (type == PIPE_SHADER_VERTEX && sub_ctx->shaders[type]) { + uint32_t fog_input = sub_ctx->shaders[next_type]->sinfo.fog_input_mask; + uint32_t fog_output = sub_ctx->shaders[type]->sinfo.fog_output_mask; + + // We only want to issue the fixup for inputs not fed by + // the outputs of the previous stage + key->vs.fog_fixup_mask = (fog_input ^ fog_output) & fog_input; + } } } } -- GitLab From cd55b80db4b6c7032e10fa9041e5e2205ed3b5d1 Mon Sep 17 00:00:00 2001 From: Gert Wollny Date: Tue, 9 Aug 2022 11:27:29 +0200 Subject: [PATCH 6/8] vrend: rebind samplers too when switching pipeline Signed-off-by: Gert Wollny Reviewed-by: Italo Nicola Part-of: --- src/vrend_renderer.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/vrend_renderer.c b/src/vrend_renderer.c index 6484979bc..8c0a4b2be 100644 --- a/src/vrend_renderer.c +++ b/src/vrend_renderer.c @@ -1650,9 +1650,10 @@ static void bind_virgl_block_loc(struct vrend_linked_shader_program *sprog, } } -static void rebind_ubo_locs(struct vrend_linked_shader_program *sprog, - enum pipe_shader_type last_shader) +static void rebind_ubo_and_sampler_locs(struct vrend_linked_shader_program *sprog, + enum pipe_shader_type last_shader) { + int next_sampler_id = 0; int next_ubo_id = 0; for (enum pipe_shader_type shader_type = PIPE_SHADER_VERTEX; @@ -1661,6 +1662,7 @@ static void rebind_ubo_locs(struct vrend_linked_shader_program *sprog, if (!sprog->ss[shader_type]) continue; + next_sampler_id = bind_sampler_locs(sprog, shader_type, next_sampler_id); next_ubo_id = bind_ubo_locs(sprog, shader_type, next_ubo_id); } @@ -2021,19 +2023,17 @@ static struct vrend_linked_shader_program *add_shader_program(struct vrend_sub_c vrend_use_program(sprog); - int next_sampler_id = 0; for (enum pipe_shader_type shader_type = PIPE_SHADER_VERTEX; shader_type <= last_shader; shader_type++) { if (!sprog->ss[shader_type]) continue; - next_sampler_id = bind_sampler_locs(sprog, shader_type, next_sampler_id); bind_const_locs(sprog, shader_type); bind_image_locs(sprog, shader_type); bind_ssbo_locs(sprog, shader_type); } - rebind_ubo_locs(sprog, last_shader); + rebind_ubo_and_sampler_locs(sprog, last_shader); if (!has_feature(feat_gles31_vertex_attrib_binding)) { if (vs->sel->sinfo.num_inputs) { @@ -5190,7 +5190,8 @@ vrend_select_program(struct vrend_sub_context *sub_ctx, ubyte vertices_per_patch int last_shader = tes_id ? PIPE_SHADER_TESS_EVAL : (gs_id ? PIPE_SHADER_GEOMETRY : PIPE_SHADER_FRAGMENT); - rebind_ubo_locs(prog, last_shader); + vrend_use_program(prog); + rebind_ubo_and_sampler_locs(prog, last_shader); } sub_ctx->last_shader_idx = sub_ctx->shaders[PIPE_SHADER_TESS_EVAL] ? PIPE_SHADER_TESS_EVAL : (sub_ctx->shaders[PIPE_SHADER_GEOMETRY] ? PIPE_SHADER_GEOMETRY : PIPE_SHADER_FRAGMENT); -- GitLab From e2cf1996f21903bf33ccd2a615060deaec87c4c8 Mon Sep 17 00:00:00 2001 From: Gert Wollny Date: Tue, 9 Aug 2022 10:23:47 +0200 Subject: [PATCH 7/8] vrend: Only rebind locations if stage was bound to different pipeline Signed-off-by: Gert Wollny Reviewed-by: Italo Nicola Part-of: --- src/vrend_renderer.c | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/vrend_renderer.c b/src/vrend_renderer.c index 8c0a4b2be..1e5f0193a 100644 --- a/src/vrend_renderer.c +++ b/src/vrend_renderer.c @@ -452,6 +452,7 @@ struct vrend_shader { struct vrend_strarray glsl_strings; GLuint id; GLuint program_id; /* only used for separable shaders */ + GLuint last_pipeline_id; uint32_t uid; bool is_compiled; bool is_linked; /* only used for separable shaders */ @@ -1267,6 +1268,7 @@ static bool vrend_compile_shader(struct vrend_sub_context *sub_ctx, if (shader->sel->sinfo.separable_program) { shader->program_id = glCreateProgram(); + shader->last_pipeline_id = 0xffffffff; glProgramParameteri(shader->program_id, GL_PROGRAM_SEPARABLE, GL_TRUE); glAttachShader(shader->program_id, shader->id); } @@ -1664,6 +1666,9 @@ static void rebind_ubo_and_sampler_locs(struct vrend_linked_shader_program *spro next_sampler_id = bind_sampler_locs(sprog, shader_type, next_sampler_id); next_ubo_id = bind_ubo_locs(sprog, shader_type, next_ubo_id); + + if (sprog->is_pipeline) + sprog->ss[shader_type]->last_pipeline_id = sprog->id.pipeline; } /* Now `next_ubo_id` is the last ubo id, which is used for the VirglBlock. */ @@ -5187,11 +5192,23 @@ vrend_select_program(struct vrend_sub_context *sub_ctx, ubyte vertices_per_patch * because it's shared across multiple pipelines and some things like * transform feedback require relinking, so we have to make sure the * blocks are bound. */ - int last_shader = tes_id ? PIPE_SHADER_TESS_EVAL : - (gs_id ? PIPE_SHADER_GEOMETRY : - PIPE_SHADER_FRAGMENT); - vrend_use_program(prog); - rebind_ubo_and_sampler_locs(prog, last_shader); + enum pipe_shader_type last_shader = tes_id ? PIPE_SHADER_TESS_EVAL : + (gs_id ? PIPE_SHADER_GEOMETRY : + PIPE_SHADER_FRAGMENT); + bool need_rebind = false; + + for (enum pipe_shader_type shader_type = PIPE_SHADER_VERTEX; + shader_type <= last_shader && !need_rebind; + shader_type++) { + if (!prog->ss[shader_type]) + continue; + need_rebind |= prog->ss[shader_type]->last_pipeline_id != prog->id.pipeline; + } + + if (need_rebind) { + vrend_use_program(prog); + rebind_ubo_and_sampler_locs(prog, last_shader); + } } sub_ctx->last_shader_idx = sub_ctx->shaders[PIPE_SHADER_TESS_EVAL] ? PIPE_SHADER_TESS_EVAL : (sub_ctx->shaders[PIPE_SHADER_GEOMETRY] ? PIPE_SHADER_GEOMETRY : PIPE_SHADER_FRAGMENT); -- GitLab From 7e6c0fac0ac37988b1578fb7754fffacc14a643a Mon Sep 17 00:00:00 2001 From: Gert Wollny Date: Fri, 5 Aug 2022 16:46:41 +0200 Subject: [PATCH 8/8] vrend: Advertise SSO handling to the guest Signed-off-by: Gert Wollny Reviewed-by: Italo Nicola Part-of: --- src/vrend_renderer.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vrend_renderer.c b/src/vrend_renderer.c index 1e5f0193a..d40e80ba2 100644 --- a/src/vrend_renderer.c +++ b/src/vrend_renderer.c @@ -11551,6 +11551,9 @@ static void vrend_renderer_fill_caps_v2(int gl_ver, int gles_ver, union virgl_c glGetIntegerv(GL_MAX_COMPUTE_UNIFORM_COMPONENTS, &max); caps->v2.max_const_buffer_size[PIPE_SHADER_COMPUTE] = max * 4; } + + if (has_feature(feat_separate_shader_objects)) + caps->v2.capability_bits_v2 |= VIRGL_CAP_V2_SSO; } void vrend_renderer_fill_caps(uint32_t set, uint32_t version, -- GitLab